This question already has answers here:
How are Python in-place operator functions different than the standard operator functions?
(2 answers)
Closed 5 years ago.
Please look into the code below
def double(arg):
print("Before: ", arg)
arg = arg * 2
print("After: ", arg)
I was studying Head first Python, and I came to this section where they were discussing about pass by value and pass by reference.
If we invoke above function with a list as argument such as:
num = [1,2,3]
double(num)
print(num)
The output is :-
Before: [1, 2, 3]
After: [1, 2, 3, 1, 2, 3]
[1, 2, 3]
Which seems fine, considering the fact that arg in function double is a new object reference. So the value of num did not change.
But if I use compound operators instead of assignment operators, things work differently as shown:
def double(arg):
print("Before: ", arg)
arg *= 2
print("After: ", arg)
num = [1,2,3]
double(num)
print(num)
The output that I get for this is:
Before: [1, 2, 3]
After: [1, 2, 3, 1, 2, 3]
[1, 2, 3, 1, 2, 3]
Why does this happen? I used to think a*=2 and a = a*2 are same. But what's going on in here?
Thanks
This is a difference between mutable and immutable objects. A mutable object can implement obj *= something by actually modifying the object in place; an immutable object can only return a new object with the updated value (in which case the result is identical to obj = obj * something). The compound assignment statements can handle either case, it's entirely up to the object's implementation.
a *= 2 modifies the structure itself (a) whereas a = a*2 reassigns a as a new variable.
(see this question)
Related
This question already has answers here:
Why does += behave unexpectedly on lists?
(9 answers)
Closed last month.
I had learned that n = n + v and n += v are the same. Until this;
def assign_value(n, v):
n += v
print(n)
l1 = [1, 2, 3]
l2 = [4, 5, 6]
assign_value(l1, l2)
print(l1)
The output will be:
[1, 2, 3, 4, 5, 6]
[1, 2, 3, 4, 5, 6]
Now when I use the expanded version:
def assign_value(n, v):
n = n + v
print(n)
l1 = [1, 2, 3]
l2 = [4, 5, 6]
assign_value(l1, l2)
print(l1)
The output will be:
[1, 2, 3, 4, 5, 6]
[1, 2, 3]
Using the += has a different result with the fully expanded operation. What is causing this?
Thats because in the first implementation you are editing the list n itself (and therefore the changes still apply when leaving the function), while on the other implementation you are creating a new temporary list with the same name, so when you leave the function the new list disappears and the variable n is linked to the original list.
the += operator works similarly to x=x+y for immutable objects (since they always create new objects), but for mutable objects such as lists they work differently. x=x+y creats a new object x while x+=y edits the current object.
It may seem counter-intuitive, but they are not always the same. In fact,
a = a + b means a = a.__add__(b), creating a new object
a += b means a = a.__iadd__(b), mutating the object
__iadd__, if absent, defaults to the __add__, but it also can (and it does, in the case of lists) mutate the original object in-place.
This works on how python treats objects and passes variables into functions.
Basically - in first example (with += )
You are passing n and v into function by "pass-by-assignment"
So n gets modified and it will be also modified out of function scope.
In second example - n is reassigned inside of the function to a new list. Which is not seen outside of the function.
In your 1st code. You changes list n itself see the below image..!
In your 2nd code. you just created a temporary list which is cleared when function call ends.. see the below images..!
In the next step when function ends the temporary list clear!!
This question already has answers here:
How do I pass a variable by reference?
(39 answers)
Closed 2 years ago.
As far as I know, python passes parameter as reference. I have following code
def func(arr):
print(arr)
if arr == [] :
return
for i in range(len(arr)):
arr[i] *= 2
func(arr[1:])
r = [1,1,1,1]
func(r)
print(r)
I would expect the output to be [2,4,8,16].
Why it outputs [2,2,2,2] as if the reference only works for one level of recursion?
maybe 'arr[1:]' always creates a new object? If that is the case, is there any way to make arr[1:] work?
You've asked a couple different questions, let's go through them one by one.
As far as I know, python passes parameter as reference.
The correct term for how python passes it's arguments is "pass py assignment". It means that parameters inside the function behave similar to how they would if they had been directly assigned with an = sign. (So, mutations will reflect across all references to the object. So far so good)
Sidenote (skip if this is confusing): For all intents and purposes, the distinction of "pass by assignment" is important because it abstracts away pass by value vs pass by reference, concepts that are not exposed in python directly. If you wish to know how the underlying mechanism works, it's actually a pass by value, but every value itself is a reference to an object (equivalent to a first level pointer in C speak). We can see why it's easier and important initially not to worry about this particular abstraction, and use "pass by assignment" as the more intuitive explanation.
Next,
maybe 'arr[1:]' always creates a new object?
Correct, slicing always creates a shallow copy of the list. docs
If that is the case, is there any way to make arr[1:] work?
Not directly, but we can use indexes instead to build a solution that works and gives us the output you desire. Just keep track of a starting index while doing the recursion, and increment it as you continue recursing.
def func(arr, start=0):
print(arr)
if arr[start:] == [] :
return
for i in range(start, len(arr)):
arr[i] *= 2
func(arr, start + 1)
r = [1,1,1,1]
func(r)
print(r)
Output:
[1, 1, 1, 1]
[2, 2, 2, 2]
[2, 4, 4, 4]
[2, 4, 8, 8]
[2, 4, 8, 16]
[2, 4, 8, 16]
You do slice arr[1:] and as the result it creates new list. That's why you got such result, in future I would not recommend you do such thing it's implicit and hard to debug. Try to return new value instead of changing it by reference when you work with functions
For example like this:
def multiplier(arr):
return [
value * (2 ** idx)
for idx, value in enumerate(arr, start=1)
]
result = multiplier([1, 1, 1, 1])
print(result) # [2, 4, 8, 16]
Slicing a list will create a new object (as you speculated), which would explain why the original list isn't updated after the first call.
Yes, arr[1:] create a new object. You can pass the index to indicate the starting index.
def func(arr, start_idx):
print(arr)
if arr == [] :
return
for i in range(start_idx, len(arr)):
arr[i] *= 2
func(arr, start_idx + 1)
r = [1,1,1,1]
func(r, 0)
print(r)
You can use this. The variable s represents start and e represents end, of the array.
def func(arr,s,e):
print(arr) #comment this line if u dont want the output steps
if s>=e:
return
for i in range(s,e):
arr[i] *= 2
func(arr,s+1,e)
r = [1,1,1,1]
func(r,0,len(r))
print(r)
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
What is the difference between LIST.append(1) and LIST = LIST + [1] (Python)
I have a doubt on how parameters are passed to functions and their mutability, especially in the case of lists.
Consider the following...
def add_list(p):
p = p + [1]
def append_list(p):
p.append(1)
p = [1, 2, 3]
add_list(p)
print p
append_list(p)
print p
The output I get is...
[1, 2, 3]
[1, 2, 3, 1]
Why does the original list change when I append to it in a function, but is unchanged if I use the operator +?
Assignment operator within a function creates a new local variable.
In the *add_list* function your p is local variable.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
“Least Astonishment” in Python: The Mutable Default Argument
def f(a, L=[]):
L.append(a)
return L
print(f(1, [1, 2]))
print(f(1))
print(f(2))
print(f(3))
I wonder why the other f(1), f(2), f(3) has not append to the first f(1, [1,2]).
I guess the result should be :
[1, 2, 1]
[1, 2, 1, 1]
[1, 2, 1, 1, 2]
[1, 2, 1, 1, 2, 3]
But the result is not this. I do not know why.
There are two different issues (better to called concepts) fused into one problem statement.
The first one is related to the SO Question as pointed by agh. The thread gives a detailed explanation and it would make no sense in explaining it again except for the sake of this thread I can just take the privilege to say that functions are first class objects and the parameters and their default values are bounded during declaration. So the parameters acts much like static parameters of a function (if something can be made possible in other languages which do not support First Class Function Objects.
The Second issue is to what List Object the parameter L is bound to. When you are passing a parameter, the List parameter passed is what L is bound to. When called without any parameters its more like bonding with a different list (the one mentioned as the default parameter) which off-course would be different from what was passed in the first call. To make the case more prominent, just change your function as follow and run the samples.
>>> def f(a, L=[]):
L.append(a)
print id(L)
return L
>>> print(f(1, [1, 2]))
56512064
[1, 2, 1]
>>> print(f(1))
51251080
[1]
>>> print(f(2))
51251080
[1, 2]
>>> print(f(3))
51251080
[1, 2, 3]
>>>
As you can see, the first call prints a different id of the parameter L contrasting to the subsequent calls. So if the Lists are different so would be the behavior and where the value is getting appended. Hopefully now it should make sense
Why you wait that's results if you call function where initialize empty list if you dont pass second argument?
For those results that you want you should use closure or global var.
This question already has answers here:
Why does += behave unexpectedly on lists?
(9 answers)
Closed 9 years ago.
>>> c = [1, 2, 3]
>>> print(c, id(c))
[1, 2, 3] 43955984
>>> c += c
>>> print(c, id(c))
[1, 2, 3, 1, 2, 3] 43955984
>>> del c
>>> c = [1, 2, 3]
>>> print(c, id(c))
[1, 2, 3] 44023976
>>> c = c + c
>>> print(c, id(c))
[1, 2, 3, 1, 2, 3] 26564048
What's the difference? are += and + not supposed to be merely syntactic sugar?
docs explain it very well, I think:
__iadd__(), etc.
These methods are called to implement the augmented arithmetic assignments (+=, -=, *=, /=, //=, %=, **=, <<=, >>=, &=, ^=, |=). These methods should attempt to do the operation in-place (modifying self) and return the result (which could be, but does not have to be, self). If a specific method is not defined, the augmented assignment falls back to the normal methods. For instance, to execute the statement x += y, where x is an instance of a class that has an __iadd__() method, x.__iadd__(y) is called.
+= are designed to implement in-place modification. in case of simple addition, new object created and it's labelled using already-used name (c).
Also, you'd notice that such behaviour of += operator only possible because of mutable nature of lists. Integers - an immutable type - won't produce the same result:
>>> c = 3
>>> print(c, id(c))
3 505389080
>>> c += c
>>> print(c, id(c))
6 505389128
They are not same
c += c append a copy of content of c to c itself
c = c + c create new object with c + c
For
foo = []
foo+=foo is syntactic sugar for foo.extend(foo) (and not foo = foo + foo)
In the first case, you're just appending members of a list into another (and not creating a new one).
The id changes in the second case because a new list is created by adding two lists. It's incidental that both are the same and the result is being bound to the same identifier than once pointed to them.
If you rephrase this question with different lists (and not c itself), it will probably become clearer.
The += operator appends the second list to the first, but the modification is in-place, so the ID remains the same.
When you use + , a new list is created, and the final "c" is a new list, so it has a different ID.
The end result is the same for both operations though.