Suppose I have this function
>>>a=3
>>>def num(a):
a=5
return a
>>>num(a)
5
>>>a
3
Value of a doesnt change.
Now consider this code :
>>> index = [1]
>>> def change(a):
a.append(2)
return a
>>> change(index)
>>> index
>>> [1,2]
In this code the value of index changes.
Could somebody please explain what is happening in these two codes. As per first code, the value of index shouldnt change(ie should remain index=[1]).
You need to understand how python names work. There is a good explanation here, and you can click here for an animation of your case.
If you actually want to operate on a separate list in your function, you need to make a new one, for instance by using
a = a[:]
before anything else. Note that this will only make a new list, but the elements will still be the same.
The value of index doesn't change. index still points to the same object it did before. However, the state of the object index points to has changed. That's just how mutable state works.
Line 3 in the first block of code is assignment and in the second block is mutation, and that's why you are observing that behavior.
The issue you are encountering is:
a = 3
def num(a):
# `a` is a reference to the argument passed, here 3.
a = 5
# Changed the reference to point at 5, and return the reference.
return a
num(a)
The a in the num function is a diffrent object than the a defined globally.
It works in case of the list because a points at the list passed, and you modify the object being referenced to by the variable, not the reference variable itself.
Related
This question already has answers here:
How to modify list entries during for loop?
(10 answers)
Closed 5 months ago.
When I try this code:
bar = [1,2,3]
print(bar)
for foo in bar:
print(id(foo))
foo = 0
print(id(foo))
print(bar)
I get this result:
[1, 2, 3]
5169664
5169676
5169652
5169676
5169640
5169676
[1, 2, 3]
I expected the end result to be [0,0,0] and that id would return identical values for each iteration. Why does it behave like this? How can I elegantly assign back to the elements of the list, without using enumerate or range(len(bar))?
See also: How to change variables fed into a for loop in list form
First of all, you cannot reassign a loop variable—well, you can, but that won’t change the list you are iterating over. So setting foo = 0 will not change the list, but only the local variable foo (which happens to contain the value for the iteration at the begin of each iteration).
Next thing, small numbers, like 0 and 1 are internally kept in a pool of small integer objects (This is a CPython implementation detail, doesn’t have to be the case!) That’s why the ID is the same for foo after you assign 0 to it. The id is basically the id of that integer object 0 in the pool.
If you want to change your list while iterating over it, you will unfortunately have to access the elements by index. So if you want to keep the output the same, but have [0, 0, 0] at the end, you will have to iterate over the indexes:
for i in range(len(bar)):
print id(bar[i])
bar[i] = 0
print id(bar[i])
print bar
Otherwise, it’s not really possible, because as soon as you store a list’s element in a variable, you have a separate reference to it that is unlinked to the one stored in the list. And as most of those objects are immutable and you create a new object when assigning a new value to a variable, you won’t get the list’s reference to update.
Yes, the output you got is the ordinary Python behavior. Assigning a new value to foo will change foo's id, and not change the values stored in bar.
If you just want a list of zeroes, you can do:
bar = [0] * len(bar)
If you want to do some more complicated logic, where the new assignment depends on the old value, you can use a list comprehension:
bar = [x * 2 for x in bar]
Or you can use map:
def double(x):
return x * 2
bar = map(double, bar)
you actually didnt change the list at all.
the first thing for loop did was to assign bar[0] to foo(equivalent to foo = bar[0]). foo is just an reference to 1. Then you assign another onject 0 to foo. This changed the reference of foo to 0. But you didnt change bar[0]. Remember, foo as a variable, references bar[0], but assign another value/object to foo doesn't affect bar[0] at all.
bar = [0 for x in bar]
Long answer : foo is just a local name, rebinding does not impact the list. Python variables are really just key:value pairs, not symbolic names for memory locations.
I was making a code, and variables started to behave strangely and get assigned to things which I thought they shouldn't. So, I decided to reduce the situation to minimal complexity in order to solve my doubts, and this is what happened:
The following code:
a = [2]
def changeA(c):
d = c
d[0] = 10
return True
changeA(a)
print(a)
prints '[10]'. This doesn't make sense to me, since I never assigned the list "a" to be anything after the first assignment. Inside the function changeA, the local variable d is assigned to be the input of the function, and it seems to me that this assignment is happening both ways, and even changing the "outside". If so, why? If not, why is this happening?
I've also noticed that the code
a = [2]
def changeA(c):
d = list(c)
d[0] = 10
return True
changeA(a)
print(a)
behaves normally (i.e., as I would expect).
EDIT: This question is being considered a duplicate of this one. I don't think this is true, since it is also relevant here that the locality character of procedures inside a function is being violated.
Python variables are references to objects, and some objects are mutable. Numbers are not, neither are strings nor tuples, but lists, sets and dicts are.
Let us look at the following Python code
a = [2] # ok a is a reference to a mutable list
b = a # b is a reference to the exact same list
b[0] = 12 # changes the value of first element of the unique list
print(a) # will display [12]
In the first example, you simply pass the reference of c(which is a) to d.
So whatever you do to d will happen on a.
In the second example, you copy the value of c(which is a) and give it to a new variable d.
So the d now has the same value as c(which is a) but different reference.
Note: you can see the reference or id of a variable using the id() function.
a = [2]
print id(a)
def changeA(c):
d = c
pirnt id(d)
d[0] = 10
return True
changeA(a)
print(a)
a = [2]
print id(a)
def changeA(c):
d = list(c)
print id(d)
d[0] = 10
return True
changeA(a)
print(a)
Its because when you do:
d = list(c)
that creates a new object. But when you do
d = c
You are making a reference to that object.
if you did
d.append(5)
to the first example you would get
[10,5]
Same operation to the second one and the list isn't modified.
Deeper explanation in the following link: http://henry.precheur.org/python/copy_list
In Python, names refer to values, so you have 2 names pointing to the same value.
In version 1 you create a list a, pass it in to the function under the pseudonym c, create another pseudonym d for the very same list, and then change the first value in that list. a, c, and d all refer to the same list.
In version 2, you're using list(c) which, to Python, means "take the contents of this iterable thing named c and make a new, different, list from it named d". Now, there are two copies of your list floating around. One is referred to as a or c, and the other is d. Therefore, when you update d[0] you're operating a second copy of the list. a remains unchanged.
'd=c' means reference copy as stated before. What it means, is that now d will reference the same object as c. As you are doing a direct manipulation on the referenced object, the value of the object a was referencing to is changed as well.
When you do 'd = list(c)' what it means that a new list object is created, with the baked of c. However, d is not referencing the same object as a anymore. Hence, the changes within the function doesn't impact a.
Here's the scenario, there's a nested list called board, and it is passed into the function, and I want to do some modification to the content to the list.
However, during the process, I found out that by assigning the variable to an empty list actually does something weird and any modifications on the board list does not affect the original board outside the function. Here's what I mean:
(It doesn't matter what the function does specifically)
This is modifyiing a particular slot in the list, and it works and does affect the original board.
def move_up(board):
board[1][2] = '.'
This is fine also since appending technically is modifying the list, this affects the original board too.
def move_up(board):
board.append(['.', 'F', '*', 'F', '.'])
This, however, doesn't work. This does not affect the original board, and I'm wondering why. The board variable shows gray, it almost feels like by doing =[], it is resetting the variable to something else, a new object, but I'm not really sure why.
def move_up(board):
board = []
board = [[1,2],[3,4]]
board[1][1] = 5
Or, is this something with the scope? Please explain how this works in Python, thanks!
Within the indented block of move_up, "board" is the local name given to the argument. There may be an object named "board" somewhere else in your program or there may not be. You wouldn't expect Python to hunt it down and modify it for you, nor would you want it to. Your function is exactly equivalent to this one:
def move_up(x):
x = []
x = [[1,2], [3,4]]
x[1][1] = 5
Lists have a function named clear that removes all the elements from the list without creating a new object, and a function named extend which appends to the list element by element.
def move_up(x):
x.clear()
x.extend([[1,2], [3,4]])
x[1][1] = 5
will do what you are trying to do.
I have a function where I work with a local variable, and then pass back the final variable after the function is complete. I want to keep a record of what this variable was before the function however the global variable is updated along with the local variable. Here is an abbreviated version of my code (its quite long)
def Turn(P,Llocal,T,oflag):
#The function here changes P, Llocal and T then passes those values back
return(P, Llocal, T, oflag)
#Later I call the function
#P and L are defined here, then I copy them to other variables to save
#the initial values
P=Pinitial
L=Linitial
P,L,T,oflag = Turn(P,L,T,oflag)
My problem is that L and Linitial are both updated exactly when Llocal is updated, but I want Linitial to not change. P doesn't change so I'm confused about what is happening here. Help? Thanks!
The whole code for brave people is here: https://docs.google.com/document/d/1e6VJnZgVqlYGgYb6X0cCIF-7-npShM7RXL9nXd_pT-o/edit
The problem is that P and L are names that are bound to objects, not values themselves. When you pass them as parameters to a function, you're actually passing a copy of the binding to P and L. That means that, if P and L are mutable objects, any changes made to them will be visible outside of the function call.
You can use the copy module to save a copy of the value of a name.
Lists are mutable. If you pass a list to a function and that function modifies the list, then you will be able to see the modifications from any other names bound to the same list.
To fix the problem try changing this line:
L = Linitial
to this:
L = Linitial[:]
This slice makes a shallow copy of the list. If you add or remove items from the list stored in L it will not change the list Lintial.
If you want to make a deep copy, use copy.deepcopy.
The same thing does not happen with P because it is an integer. Integers are immutable.
In Python, a variable is just a reference to an object or value in the memory. For example, when you have a list x:
x = [1, 2, 3]
So, when you assign x to another variable, let's call it y, you are just creating a new reference (y) to the object referenced by x (the [1, 2, 3] list).
y = x
When you update x, you are actually updating the object pointed by x, i.e. the list [1, 2, 3]. As y references the same value, it appears to be updated too.
Keep in mind, variables are just references to objects.
If you really want to copy a list, you shoud do:
new_list = old_list[:]
Here's a nice explanation: http://henry.precheur.org/python/copy_list
This question already has answers here:
"Least Astonishment" and the Mutable Default Argument
(33 answers)
Closed 6 months ago.
#!/usr/bin/env python3.2
def f1(a, l=[]):
l.append(a)
return(l)
print(f1(1))
print(f1(1))
print(f1(1))
def f2(a, b=1):
b = b + 1
return(a+b)
print(f2(1))
print(f2(1))
print(f2(1))
In f1 the argument l has a default value assignment, and it is only evaluated once, so the three print output 1, 2, and 3. Why f2 doesn't do the similar?
Conclusion:
To make what I learned easier to navigate for future readers of this thread, I summarize as the following:
I found this nice tutorial on the topic.
I made some simple example programs to compare the difference between mutation, rebinding, copying value, and assignment operator.
This is covered in detail in a relatively popular SO question, but I'll try to explain the issue in your particular context.
When your declare your function, the default parameters get evaluated at that moment. It does not refresh every time you call the function.
The reason why your functions behave differently is because you are treating them differently. In f1 you are mutating the object, while in f2 you are creating a new integer object and assigning it into b. You are not modifying b here, you are reassigning it. It is a different object now. In f1, you keep the same object around.
Consider an alternative function:
def f3(a, l= []):
l = l + [a]
return l
This behaves like f2 and doesn't keep appending to the default list. This is because it is creating a new l without ever modifying the object in the default parameter.
Common style in python is to assign the default parameter of None, then assign a new list. This gets around this whole ambiguity.
def f1(a, l = None):
if l is None:
l = []
l.append(a)
return l
Because in f2 the name b is rebound, whereas in f1 the object l is mutated.
This is a slightly tricky case. It makes sense when you have a good understanding of how Python treats names and objects. You should strive to develop this understanding as soon as possible if you're learning Python, because it is central to absolutely everything you do in Python.
Names in Python are things like a, f1, b. They exist only within certain scopes (i.e. you can't use b outside the function that uses it). At runtime a name refers to a value, but can at any time be rebound to a new value with assignment statements like:
a = 5
b = a
a = 7
Values are created at some point in your program, and can be referred to by names, but also by slots in lists or other data structures. In the above the name a is bound to the value 5, and later rebound to the value 7. This has no effect on the value 5, which is always the value 5 no matter how many names are currently bound to it.
The assignment to b on the other hand, makes binds the name b to the value referred to by a at that point in time. Rebinding the name a afterwards has no effect on the value 5, and so has no effect on the name b which is also bound to the value 5.
Assignment always works this way in Python. It never has any effect on values. (Except that some objects contain "names"; rebinding those names obviously effects the object containing the name, but it doesn't affect the values the name referred to before or after the change)
Whenever you see a name on the left side of an assignment statement, you're (re)binding the name. Whenever you see a name in any other context, you're retrieving the (current) value referred to by that name.
With that out of the way, we can see what's going on in your example.
When Python executes a function definition, it evaluates the expressions used for default arguments and remembers them somewhere sneaky off to the side. After this:
def f1(a, l=[]):
l.append(a)
return(l)
l is not anything, because l is only a name within the scope of the function f1, and we're not inside that function. However, the value [] is stored away somewhere.
When Python execution transfers into a call to f1, it binds all the argument names (a and l) to appropriate values - either the values passed in by the caller, or the default values created when the function was defined. So when Python beings executing the call f3(5), the name a will be bound to the value 5 and the name l will be bound to our default list.
When Python executes l.append(a), there's no assignment in sight, so we're referring to the current values of l and a. So if this is to have any effect on l at all, it can only do so by modifying the value that l refers to, and indeed it does. The append method of a list modifies the list by adding an item to the end. So after this our list value, which is still the same value stored to be the default argument of f1, has now had 5 (the current value of a) appended to it, and looks like [5].
Then we return l. But we've modified the default list, so it will affect any future calls. But also, we've returned the default list, so any other modifications to the value we returned will affect any future calls!
Now, consider f2:
def f2(a, b=1):
b = b + 1
return(a+b)
Here, as before, the value 1 is squirreled away somewhere to serve as the default value for b, and when we begin executing f2(5) call the name a will become bound to the argument 5, and the name b will become bound to the default value 1.
But then we execute the assignment statement. b appears on the left side of the assignment statement, so we're rebinding the name b. First Python works out b + 1, which is 6, then binds b to that value. Now b is bound to the value 6. But the default value for the function hasn't been affected: 1 is still 1!
Hopefully that's cleared things up. You really need to be able to think in terms of names which refer to values and can be rebound to point to different values, in order to understand Python.
It's probably also worth pointing out a tricky case. The rule I gave above (about assignment always binding names with no effect on the value, so if anything else affects a name it must do it by altering the value) are true of standard assignment, but not always of the "augmented" assignment operators like +=, -= and *=.
What these do unfortunately depends on what you use them on. In:
x += y
this normally behaves like:
x = x + y
i.e. it calculates a new value with and rebinds x to that value, with no effect on the old value. But if x is a list, then it actually modifies the value that x refers to! So be careful of that case.
In f1 you are storing the value in an array or better yet in Python a list where as in f2 your operating on the values passed. Thats my interpretation on it. I may be wrong
Other answers explain why this is happening, but I think there should be some discussion of what to do if you want to get new objects. Many classes have the method .copy() that allows you create copies. For instance, if we rewrite f1 as
def f1(a, l=[]):
new_l = l.copy()
new_l.append(a)
return(new_l)
then it will continue to return [1]no matter how many times we call it. There is also the library https://docs.python.org/3/library/copy.html for managing copies.
Also, if you're looping through the elements of a container and mutating them one by one, using comprehensions not only is more Pythonic, but can avoid the issue of mutating the original object. For instance, suppose we have the following code:
data = [1,2,3]
scaled_data = data
for i, value in enumerate(scaled_data):
scaled_data[i] = value/sum(data)
This will set scaled_data to [0.16666666666666666, 0.38709677419354843, 0.8441754916792739]; each time you set a value of scaled_data to the scaled version, you also change the value in data. If you instead have
data = [1,2,3]
scaled_data = [x/sum(data) for x in data]
this will set scaled_data to [0.16666666666666666, 0.3333333333333333, 0.5] because you're not mutating the original object but creating a new one.