The intended objective of function foo is to add the number provided as argument to the list and, in case it is 0, reset the list. First I wrote this program:
def foo(n, bar = []):
if n == 0:
bar = []
print("list empty")
else:
bar.append(n)
for y in bar:
print(y, end=', ')
print()
foo(5)
foo(3)
foo(0)
foo(6)
output:
5,
5, 3,
list empty
5, 3, 6,
but it looks like bar = [] is ignored. Then I changed bar = [] with bar.clear() and it works as I thought:
def foo(n, bar = []):
if n == 0:
bar.clear()
print("list empty")
else:
bar.append(n)
for y in bar:
print(y, end=', ')
print()
foo(5)
foo(3)
foo(0)
foo(6)
output:
5,
5, 3,
list empty
6,
I haven't understood why bar.clear() works differntly from bar = [] since clear() should
Remove all elements from the set.
so the same thing bar = [] does.
Edit: I don't think my question is a duplicate of “Least Astonishment” and the Mutable Default Argument, I'm aware that
The default value is evaluated only once.
(from the official tutorial) but what I am asking is, why bar = [] doesn't edit (in this case clear) the list, while append and clear does?
bar = [] rebinds bar; it creates a brand new empty list, and binds the name bar to it. It doesn't matter what bar was before; it could have been an int, a tuple, or dict, and it's irrelevant now, because it's now bound to a brand new list. The old binding no longer exists in the current method call, but the original list remains in existence as the value of the default argument to foo (note: Mutable defaults are considered surprising and ugly because of this confusion).
bar.clear() calls a method on the existing list, which tells that list to clear itself (and has no effect on what bar refers to; it better be something with a clear method, but other than that, it's not really required that it be a list). Since it's the same bar you started with (referencing the same mutable default value cached in the function defaults), that affects your future calls (because bar continues to refer to the list you provided as the mutable default).
The thing you should really understand is how variables work in Python.
The variables are always just references to the actual content. Nothing is stored in the variable itself it just points to the unnamed content.
Now, when you call foo() what you have is a list in memory and the local bar variable is pointing to that. If you call bar.clear(), that list is actually cleared and that's what you want. However, if you say bar = [] you just bind the local bar variable to a new, empty list and the other list (that already contains 5 and 3) remains intact and it will be used the next time you call foo().
Here in you code bar is the reference to some list located in memory.
When you use bar = [], the variable bar becomes associated with a new (empty) list. When you use bar.clear(), you modify current list.
Speaking generally, this means that lists in Python are passed by reference and not by value. You can read more about it here.
When you do a list.clear(), the list gets cleared :-)
When you do a list = [], the list gets overwritten by a new empty list.
The difference is that if something was referencing the list before if was overwritten, it will still be referencing the values you think now has been cleared.
This is an excellent way of introducing extremely hard to debug bugs.
Let's say you have a list containing some values, and you assign it to a variable named foo.
foo = [0, 1]
This variable now points to this list in memory.
Let's say you then create a new variable named bar and assign to foo.
bar = foo
These two variables now point to the same list object in memory. If you then change foo and have it equal [], what happens to bar?
foo = []
bar == [0, 1] # True
Now let's do the same with foo.clear().
foo.clear()
bar == [] # True
This is because using the clear method on a list clears that list object, which affects everything that references this object, while assigning a variable to an empty list only changes what that variables, and doesn't change the original list whatsoever.
this has desired property:
def foo(n, bar = []):
if n == 0:
bar[:] = []
print("list empty")
else:
bar.append(n)
for y in bar:
print(y, end=', ')
print()
Related
The first block of code works (lines 15 - 21).
The error is occurring in the second block (lines 24 - 30).
Here is my code:
# converting numerical input into list and tuple <-- line 15
data = input("provide numbers separated by ',': ")
list = data.split(",")
tuple = tuple(list)
print("list:", list, "tuple:", tuple)
def convert(): # <-- line 24
data = input("provide numbers separated by ',': ")
list = data.split(',')
tuple = tuple(list)
print("list:", list, "tuple:", tuple)
convert()
I have read solutions that suggest declaring the variable in the global namespace, but the solutions do not seem to be working for my function. And a solution still eludes me.
The function has the same code as the first block, its just wrapped in a function. I don't understand why the code works at the top level, but not inside a function.
Does anyone know what's happening here?
If you re-bind a non-global variable name (such as a = b) anywhere within a function(1), it is made a local variable for that entire function (even before the modification).
That means that tuple = tuple(list) is assigning to a local tuple, and your use of tuple(list) is using that variable, rather than the actual built-in tuple() function. Since that variable name is not bound at this point, you get the "use before set" error.
This is why it's a bad idea to use built-in function names as variable names, which is something you appear to have done with abandon, both tuple and list :-)
So I would suggest two things:
rename your variables to prevent clashes (use my_list and my_tuple for example).
make my_tuple global within the function (better would be to avoid globals altogether).
Avoiding globals can be as simple as:
def convert():
data = input("provide numbers separated by ',': ")
my_list = data.split(",")
my_tuple = tuple(my_list)
print("list:", my_list, "tuple:", my_tuple)
return my_tuple
outer_tuple = convert()
(1) This does not include simply modifying the variable's value such as changing one item in a list:
x = [1, 2, 3]
x[1] = 42 # Mutation of x, not re-binding.
x = [7, 8, 9] # Re-binding x to new object.
And note that for immutable types (like int for example), x = 7 is re-binding x to a different object 7, not changing the value of the object "behind" x. This particular aspect of Python (the fact that all things are objects and variable names are simply bound to those objects) was one of my greatest epiphanies when learning the language.
I want to make a function that takes makes a list and adds an item to that list when I run it, so basically I would be passing this function two arguments, the first is the name I want the list to have, the second is an item I want to add to the list.
I want to do this as a learning excercise and I've built a function that almost does what I want it to do.
def addlst(l, item):
"""add an item to a list"""
l = list()#this line essentially does nothing.
if type(item) == str:
l.append(item.capitalize())
return l
print(l)
else:
l.append(item)
return l
print(l)
if I pass this something like:
addlst(people, 'kev')
I get the error:
NameError: name 'people' is not defined
but obviously, if I define people as an empty list it works fine.
Is what I'm doing actually possible? I know that as it stands the line
l = list()
would just empty the list first and so the append function would be useless (I'd have to add another clause to check if the list exists already) but my question is really about initialising a blank list within a function and then returning it to the global scope.
Putting aside the discussion regarding whether it is a good practice, (which can make sens if your main goal is about improving your understanding), you could simply use the global keyword to do what you describe. Say
def f(el):
global l
l.append(el)
Then
>>> l = []
>>> f(2)
>>> l
[2]
>>> f(3)
>>> l
[2, 3]
As it reads above, l has to be declared before using f.
Dealing with your peculiarities, something you could do is:
def addlst(item):
"""add an item to a list"""
global l#
if isinstance(item, str): # type(item) == str is not recommanded
item = item.capitalize()
l.append(item)
But actually, note that doing so will "bind" your function to deal exclusively with the list named l in the global scope. And it looks like this is not what you want, since it appears that you want to be able to pass multiple list objects to your function. The best approach here is
def addlst(list_, item):
"""add an item to a list"""
if isinstance(item, str):
item = item.capitalize()
list_.append(item)
return list_
First off: a function should never inject a new name into the calling scope.
If the function works with a global variable, that needs to be documented and the caller has to ensure the global exists before calling it.
If the function takes an argument, there are two options. One, you can mutate it and have your function return None, or you can create a new value based on the argument and return that, leaving the argument unchanged. Very rarely, if ever, should your function modify an argument and return a value.
If you have your function return a new list, you can optionally take a list to modify, or create a brand new list inside your function.
Unrelated, but you shouldn't care what the type of item is, only that it is something that has a capitalize method that you can call. Just try it; if it doesn't, it will raise an AttributeError that you can catch, in which case you can simply use item as is.
Putting all this together, I recommend the third approach. add_to_list will take an item as the first argument, and an optional list as the second argument. If no list is given, the function will create a new list. In either case, you'll append the appropriately modified item to the list and return it.
def add_to_list(item, l=None):
# Note: this doesn't change the item you pass; it just rebinds
# the local name item to a *new* string if it succeeds.
try:
item = item.capitalize()
except AttributeError:
pass
if l is None:
l = []
return l + [item]
Then you can use
people = add_to_list('kev') # people == ['Kev']
people = add_to_list('bob') # people == ['Bob'], Kev is gone!
people = add_to_list('kev', people) # people == ['Bob', 'Kev'], Kev is back.
The more efficient version mentioned in the second approach modifies l in place; in this case, though, you have to provide a list; you can't create a new list.
def add_to_list(item, l):
try:
item = item.capitalize()
except AttributeError:
pass
l.append(item)
people = [] # Create the list in the *calling* scope, not in add_to_list
add_to_list('kev') # TypeError, missing an argument
add_to_list('kev', people) # people == ['Kev']
add_to_list('bob', people) # people == ['Kev', 'Bob']
The first approach is pretty poor; it restricts your function to working with a specific list whose name is hard-coded in the function, but I'll mention it here for completeness. Since the list is hard-coded, we'll change the name of the function to reflect that.
def add_to_people(item):
global people
try:
item = item.capitalize()
except AttributeError:
pass
people.append(item)
Now add_to_list can work with the global list people, but no other list.
people = []
add_to_people('kev')
add_to_people('bob')
And finally, in the interest of full disclosure, yes, add_to_people can create the list if it hasn't already:
def add_to_people(item):
global people
try:
people # Simply try to evaluate the name
except NameError:
people = []
# ...
However, if using a global name in the first place is bad, autovivifying it like this is worse. Avoid this style of programming wherever possible.
When you write addlst(people, 'kev'), you're telling your code that you want to execute the addlst function with a variable named people as first parameter.
Problem is: you never set this variable!
There are many ways to this; You could either initialize an empty list before calling the function:
people = []
addlst(people, 'kev')
Or make the parameter optional with a default value:
def addlst(item, l = None):
"""add an item to a list"""
if l is None:
l = []
if type(item) == str:
l.append(item.capitalize())
return l
else:
l.append(item)
return l
But that could be tricky, since lists are mutable objects.
NB: In your function, print will never be called because it stands after the return statement.
A last way
Eventually, you could also shorten your code by doing something like that:
mylist = []
item = "test"
mylist.append(item.capitalize() if type(item) == str else item)
This question already has answers here:
Why can a function modify some arguments as perceived by the caller, but not others?
(13 answers)
Closed 3 years ago.
Let's take a simple code:
y = [1,2,3]
def plusOne(y):
for x in range(len(y)):
y[x] += 1
return y
print plusOne(y), y
a = 2
def plusOne2(a):
a += 1
return a
print plusOne2(a), a
Values of 'y' change but value 'a' stays the same. I have already learned that it's because one is mutable and the other is not. But how to change the code so that the function doesn't change the list?
For example to do something like that (in pseudocode for simplicity):
a = [1,2,3,...,n]
function doSomething(x):
do stuff with x
return x
b = doSomething(a)
if someOperation(a) > someOperation(b):
do stuff
EDIT: Sorry, but I have another question on nested lists. See this code:
def change(y):
yN = y[:]
for i in range(len(yN)):
if yN[i][0] == 1:
yN[i][0] = 0
else:
yN[i][0] = 1
return yN
data1 = [[1],[1],[0],[0]]
data2 = change(data1)
Here it doesn't work. Why? Again: how to avoid this problem? I understand why it is not working: yN = y[:] copies values of y to yN, but the values are also lists, so the operation would have to be doubled for every list in list. How to do this operation with nested lists?
Python variables contain pointers, or references, to objects. All values (even integers) are objects, and assignment changes the variable to point to a different object. It does not store a new value in the variable, it changes the variable to refer or point to a different object. For this reason many people say that Python doesn't have "variables," it has "names," and the = operation doesn't "assign a value to a variable," but rather "binds a name to an object."
In plusOne you are modifying (or "mutating") the contents of y but never change what y itself refers to. It stays pointing to the same list, the one you passed in to the function. The global variable y and the local variable y refer to the same list, so the changes are visible using either variable. Since you changed the contents of the object that was passed in, there is actually no reason to return y (in fact, returning None is what Python itself does for operations like this that modify a list "in place" -- values are returned by operations that create new objects rather than mutating existing ones).
In plusOne2 you are changing the local variable a to refer to a different integer object, 3. ("Binding the name a to the object 3.") The global variable a is not changed by this and continues to point to 2.
If you don't want to change a list passed in, make a copy of it and change that. Then your function should return the new list since it's one of those operations that creates a new object, and the new object will be lost if you don't return it. You can do this as the first line of the function: x = x[:] for example (as others have pointed out). Or, if it might be useful to have the function called either way, you can have the caller pass in x[:] if he wants a copy made.
Create a copy of the list. Using testList = inputList[:]. See the code
>>> def plusOne(y):
newY = y[:]
for x in range(len(newY)):
newY[x] += 1
return newY
>>> y = [1, 2, 3]
>>> print plusOne(y), y
[2, 3, 4] [1, 2, 3]
Or, you can create a new list in the function
>>> def plusOne(y):
newList = []
for elem in y:
newList.append(elem+1)
return newList
You can also use a comprehension as others have pointed out.
>>> def plusOne(y):
return [elem+1 for elem in y]
You can pass a copy of your list, using slice notation:
print plusOne(y[:]), y
Or the better way would be to create the copy of list in the function itself, so that the caller don't have to worry about the possible modification:
def plusOne(y):
y_copy = y[:]
and work on y_copy instead.
Or as pointed out by #abarnet in comments, you can modify the function to use list comprehension, which will create a new list altogether:
return [x + 1 for x in y]
Just create a new list with the values you want in it and return that instead.
def plus_one(sequence):
return [el + 1 for el in sequence]
As others have pointed out, you should use newlist = original[:] or newlist = list(original) to copy the list if you do not want to modify the original.
def plusOne(y):
y2 = list(y) # copy the list over, y2 = y[:] also works
for i, _ in enumerate(y2):
y2[i] += 1
return y2
However, you can acheive your desired output with a list comprehension
def plusOne(y):
return [i+1 for i in y]
This will iterate over the values in y and create a new list by adding one to each of them
To answer your edited question:
Copying nested data structures is called deep copying. To do this in Python, use deepcopy() within the copy module.
You can do that by make a function and call this function by map function ,
map function will call the add function and give it the value after that it will print the new value like that:
def add(x):
return x+x
print(list(map(add,[1,2,3])))
Or you can use (*range) function it is very easy to do that like that example :
print ([i+i for i in [*range (1,10)]])
First of all, I understand that I can use global statement to access global variables. But somehow I was able to modify a global list without global like below:
def func1(nums):
nums = [4,5,6]
nums = [1,2,3]
func1(nums)
print nums # print [1,2,3]
def func2(nums):
nums[0] = 4
nums[1] = 5
nums[2] = 6
nums = [1,2,3]
func2(nums)
print nums # print [4,5,6]
After trying func2, I realized that I can always access global list in a function if I specify the index:
def func3(nums):
nums[:] = [4,5,6]
nums = [1,2,3]
func3(nums)
print nums # print [4,5,6]
Is it because Python automatically goes trying to match a global variable if a function variable is used before definition?
I understand that I can use global statement to access global variables
Your understanding is wrong. You can always access a global variable as long as you don't have a local variable of the same name. You only need the global statement when you are going to change what object a variable name refers to. In your func2, you are not doing that; you are only changing the contents of the object. nums still refers to the same list.
It is of concept based on mutable and immutable objects in Python. In your case, for example:
a=[1,2]
def myfn():
a=[3,4]
print id(a)
>>>id(a)
3065250924L
>>>myfn()
3055359596
It is clear both are different objects. Now:
a=[1,2]
def myfn():
a[:] =[3,4]
print id(a)
>>>id(a)
3055358572
>>>myfn()
3055358572
That means it is same variable using in local and global scope.
In this specific case it is because lists are mutable.
As a result having them in the global namespace, or even passed through a function, means that they will be changed as Python holds the reference to the mutable object, not a copy of it.
If you try the same thing with tuples it will not work, as they are immutable.
The way to avoid this is to provide a copy of the list to the function, not the list itself:
func2(list[:])
At the same time you can do this with default arguments, where you can specify a default argument to be [], and if you then .append() something to it, that default argument will forever hold that item within it for all future calls (unless you remove it in some way).
2 variables nums are different and they point to a same object or 2 different objects, though they have same name.
when you call func1(nums), mean that you pass a reference. Now the 2 variable nums point to same object. (2 variables, 1 object)
when you assign in func1, the inside variable nums will point to a new object, the outside is still unchanged (2 variables, 2 object)
and when you call print nums then this nums is the outside variable,
There are two reasons for this result:
Variables are simply names that refer to objects
List is mutable
In func1,nums refer to a new object because a new list is created. Therefore global nums is not affected.
In func2, the modification is applied to the object passed in. Thus global nums is changed. A new object is not created because list is mutable.
ref: https://docs.python.org/3/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python
In this case, I want that the program print "X = changed"
class Clase:
def __init__(self,variable):
self.var = variable
def set_var(self):
self.var = 'changed'
X = 'unchanged'
V = Clase(X)
V.set_var()
print "X = ",X
All values are objects and are passed by reference in Python, and assignment changes the reference.
def myfunc(y):
y = 13
x = 42 # x now points at the integer object, 42
myfunc(y) # inside myfunc, y initially points to 42,
# but myfunc changes its y to point to a
# different object, 13
print(x) # prints 42, since changing y inside myfunc
# does not change any other variable
It's important to note here that there are no "simple types" as there are in other languages. In Python, integers are objects. Floats are objects. Bools are objects. And assignment is always changing a pointer to refer to a different object, whatever the type of that object.
Thus, it's not possible to "assign through" a reference and change someone else's variable. You can, however, simulate this by passing a mutable container (e.g. a list or a dictionary) and changing the contents of the container, as others have shown.
This kind of mutation of arguments through pointers is common in C/C++ and is generally used to work around the fact that a function can only have a single return value. Python will happily create tuples for you in the return statement and unpack them to multiple variables on the other side, making it easy to return multiple values, so this isn't an issue. Just return all the values you want to return. Here is a trivial example:
def myfunc(x, y, z):
return x * 2, y + 5, z - 3
On the other side:
a, b, c = myFunc(4, 5, 6)
In practice, then, there is rarely any reason to need to do what you're trying to do in Python.
In python list and dict types are global and are passed around by reference. So if you change the type of your variable X to one of those you will get the desired results.
[EDIT: Added use case that op needed]
class Clase:
def __init__(self,variable):
self.var = variable
def set_var(self):
self.var.test = 'changed'
class ComplicatedClass():
def __init__(self, test):
self.test = test
X = ComplicatedClass('unchanged')
print('Before:', X.test)
V = Clase(X)
V.set_var()
print("After:",X.test)
>>> Before: unchanged
>>> After: changed
strings are immutable so you could not change X in this way
... an alternative might be reassigning X in the global space... this obviously will fail in many many senarios (ie it is not a global)
class Clase:
def __init__(self,variable):
self.var = variable
def set_var(self):
globals()[self.var] = 'changed'
X = 'unchanged'
V = Clase('X')
V.set_var()
print "X = ",X
the other alternative is to use a mutable data type as suggested by Ashwin
or the best option is that this is probably not a good idea and you should likely not do it...