What happens when I reassign the mutable default argument inside a function? - python

I know this has been answered in highly active 'least astonishment' question, I modified code snippet slightly but still don't understand why it produces new empty list and adds 1 to it..
def foo(var=[]):
if len(var) == 0:
var = []
print(var)
var.append(1)
return var
print(foo())
print(foo())
outputs:
[]
[1]
[]
[1]
My expected logic of this python snippet:
on the first call of foo(), var is initialized to empty list and indeed - evaluated at definition of foo ONCE - as the popular question answered.
That if clause check should not be entered at all - because var is initialized only once, on the 2nd call foo() it should simply skip it and add 1 to var = [ 1 ] thus making the result [1,1] after the 2nd call.
But it does still go to if clause and init var to [] every time.. why?
However, if I remove the if clause:
def foo(var=[]):
print(var)
var.append(1)
return var
print(foo())
print(foo())
It does append "1" to var every time and var grows:
[1]
[1,1]
So the if clause is entered and checked for validity.. confused about the execution..

Let's rewrite your example so that we make some change to the argument before reassigning:
In [262]: def foo(var=[]):
...: var.append(1) # 1
...: print(var) # 2
...: var = [] # 3
...: return var # 4
...:
In [263]: foo()
[1]
Out[263]: []
In [264]: foo()
[1, 1] # reflects the append from the previous call
Out[264]: []
The append step in line 1 mutates the default argument list. The reassignment step in line 3 simply reassigns the variable var to a new list (a completely different object), that's what you return.
You'll see each subsequent call modifies the default argument list, it's still there but you just don't see it because you lose the reference to it when you reassign.
I recommend reading this article by Ned Batchelder.

alright, so the two things:
var in your function definition is assigned to an empty list i.e [] that is good,
var in the if statement is again being re-assigned but is the same thing, an empty list, this you don't really need as it's preventing you from achieving [1,1]
this may help clear things of why you're "expecting" a var = [1,1] the first time around:
def foo(var=[]):
if len(var) == 0:
print(var)
var.append(1)
return var
print(foo()) # returns [],[1]
print(foo()) # returns [1,1] this list appended another 1 in because when you ran this instance of foo, this list was not empty anymore rather filled with 1 and bypassed the if statement and appended another 1 resulting to [1,1]
thus, you don't really need that var = [] in the if statement as it confuses the next steps as to what you want to achieve...
hope that helps somewhat :)

var=[] on line 1 gets evaluated once when the function is defined
var=[] on line 3 gets evaluated every time you pass a var with len(var) == 0, for instance when you pass no args and the default is used.
This means that the [] on line 3 is a new list every time the function is called, as that line of code is executed every time the function is called.

Related

Why can't I change the default value of a function after it was defined?

i = 5
def f(arg=i):
print(arg)
i = 6
f()
I am learning Python from the official documentation. There I find the above piece of code which I am unable to understand as to why 5 is printed instead of 6. I am relatively new to Python. Can somebody help me understand the concept?
def f(arg=i) says "make me a function f where the default value for arg is whatever i is right now". At the time of defining the function, i=5.
i = 5
def f(arg=i)
print(arg)
The i is evaluated at the time of definition, so the code above has the same meaning as the code below:
def f(arg=5)
print(arg)
This means that, when the function is called without arguments, arg will have the value 5, no matter what the value of i is now.
In order to get what you want, just do the following:
def f(arg)
print(arg)
i = 6
f(i)
Because the function takes its default value on the first declaration of 'i'.
Change to i=6 on the first line if you want you code to print 6.
Hope I helped !
This is the difference between something being handled by reference vs by value. When you defined the function f you told it to set the argument's default value to i this is done by value, not by reference, so it took whatever the value of i was at that time and set the default for the function to that. Changing the value of i after that point does not change the value of arg. If you want it to work that way you could do this:
i = 5
def f(arg = None):
if (arg = None)
arg = i
print(arg)
i = 6
f()
This lets you pass a value for arg into the function as normal, but if you don't (or you explicitly pass None) it updates arg to the current value of i if arg is still None (Python's version of NULL if you're familiar with other languages)
Something similar can be done using the or operator, arg = arg or i,but that will check if arg is falsy, and when using integers like you are in your example, 0 will be caught by the check.
What others have said is true...the default is evaluated at the time of function creation, but it is not that it takes the "value of i" at the time of creation. The default is assigned the object referred to by "i" at the time of creation. This is an important point, because if that object is mutable, the default can be changed!
Here's what happens:
import inspect
i = 5 # name "i" refers to an immutable Python integer object of value 5.
print(f'i = {i} (id={id(i)})') # Note value and ID
# Create function "f" with a parameter whose default is the object
# referred to by name "i" *at this point*.
def f(arg=i):
print(f'arg = {arg} (id={id(arg)})')
# Use the inspect module to extract the defaults from the function.
# Note the value and ID
defaults = dict(inspect.getmembers(f))['__defaults__']
print(f'defaults = {defaults} (id={id(defaults[0])})')
# name "i" now refers to a different immutable Python integer object of value 6.
i = 6
print(f'i = {i} (id={id(i)})') # Note value and ID (changed!)
f() # default for function still referes to object 5.
f(i) # override the default with object currently referred to by name "i"
Output:
i = 5 (id=2731452426672) # Original object
defaults = (5,) (id=2731452426672) # default refers to same object
i = 6 (id=2731452426704) # Different object
arg = 5 (id=2731452426672) # f() default is the original object
arg = 6 (id=2731452426704) # f(i) parameter is different object
Now see the results of a mutable default:
import inspect
i = [5] # name "i" refers to an mutable Python list containing immutable integer object 5
print(f'i = {i} (id={id(i)})') # Note value and ID
# Create function "f" with a parameter whose default is the object
# referred to by name "i" *at this point*.
def f(arg=i):
print(f'arg = {arg} (id={id(arg)})')
# Use the inspect module to extract the defaults from the function.
# Note the value and ID
defaults = dict(inspect.getmembers(f))['__defaults__']
print(f'defaults = {defaults} (id={id(defaults[0])})')
# name "i" now refers to a different immutable Python integer object of value 6.
i[0] = 6 # MUTATE the content of the object "i" refers to.
print(f'i = {i} (id={id(i)})') # Note value and ID (UNCHANGED!)
f() # default for function still refers to original list object, but content changed!
i = [7] # Create a different list object
print(f'i = {i} (id={id(i)})') # Note value and ID (changed)
f(i) # override the default currently refered to by name "i"
Output:
i = [5] (id=2206901216704) # Original object
defaults = ([5],) (id=2206901216704) # default refers to original object
i = [6] (id=2206901216704) # Still original object, but content changed!
arg = [6] (id=2206901216704) # f() default refers to orginal object, but content changed!
i = [7] (id=2206901199296) # Create a new list object
arg = [7] (id=2206901199296) # f(i) parameter refers to new passed object.
This can have strange side effects if not understood well:
>>> def f(a,b=[]): # mutable default
... b.append(a)
... return b
...
>>> x = f(1)
>>> x
[1]
>>> y = f(2) # some would think this would return [2]
>>> y
[1, 2]
>>> x # x changed from [1] to [1,2] as well!
[1, 2]
Above, b refers to the original default list object. Appending to it mutates the default list. Returning it makes x refer to the same object. The default list now contains [1] so appending in the 2nd call make it [1,2]. y refers to the same default object as x so both names refer see the same object content.
To fix, make the default immutable and create a new list when the default is seen:
>>> def f(a,b=None):
... if b is None:
... b = []
... b.append(a)
... return b
...
>>> f(1)
[1]
>>> f(2)
[2]
This is because you are assigning the value when the function is created. arg at the time of creation will be defaulted to what i is in that moment. Since at the time of the function being created the value of i is 5 then that's what the default value of that argument becomes. After the initial creation of the function i in the function argument is no longer linked to i in the body.

why python can't access global variable value with function parameter name change?

in these two pieces of code, why the second one gives error about local variable assignment? two codes are similar just function parameters is different, in the second one it's able to read the global variable why not in the first one what changes with parameter name change about symbol table?
first one:
def a(z):
z+=1
z=3
a(z)
second one:
def a(z):
b += 1
b = 5
a(b)
There aren't any global variables in use here.
In the first example, z is a parameter to the function, not a global. Note that when you increment z, it does not change in the calling scope, because the z inside the function is a copy of the z you passed in from outside the function.
In the second example, there is no b inside the function (the parameter is z), which is why you get an error inside the function when you try to modify it.
To do what you're trying to do, you should make the parameter a mutable object that contains the value you're trying to mutate; that way you can modify the value inside the function and the caller will have access to the new value. You could define a class for this purpose, or you could simply make it a single-element list:
def a(z):
z[0] += 1
b = [5]
a(b) # b == [6]
Or, if possible, a better approach IMO is not to depend on mutability, and to just return the new value, requiring the caller to explicitly re-assign it within its scope:
def a(z)
return z + 1
b = 5
b = a(b) # b == 6
You should concider def blocks as stand alone.
In the first snippet:
def a(z):
z+=1
What is z ? It's the first parameter of a
In the second snippet:
def a(z):
b += 1
What is b ? It is unknown. That's why this code fails.
You should also notice that in your first snippet, z inside the function is not the same than z=3:
>>> def a(z):
... z+=1
...
>>> z=3
>>> a(z)
>>>
>>> z
3
In the second code, the parameter is "z", and you tried to access that parameter with "b"
def a(z):
b += 1

var passed to a function by kwargs is not updated [duplicate]

This question already has answers here:
How do I pass a variable by reference?
(39 answers)
Closed 8 months ago.
In some languages you can pass a parameter by reference or value by using a special reserved word like ref or val. When you pass a parameter to a Python function it never alters the value of the parameter on leaving the function.The only way to do this is by using the global reserved word (or as i understand it currently).
Example 1:
k = 2
def foo (n):
n = n * n #clarity regarding comment below
square = n
return square
j = foo(k)
print j
print k
would show
>>4
>>2
showing k to be unchanged.
In this example the variable n is never changed
Example 2:
n = 0
def foo():
global n
n = n * n
return n
In this example the variable n is changed.
Is there any way in Python to call a function and tell Python that the parameter is either a value or reference parameter instead of using global?
There are essentially three kinds of 'function calls':
Pass by value
Pass by reference
Pass by object reference
Python is a PASS-BY-OBJECT-REFERENCE programming language.
Firstly, it is important to understand that a variable, and the value of the variable (the object) are two seperate things. The variable 'points to' the object. The variable is not the object. Again:
THE VARIABLE IS NOT THE OBJECT
Example: in the following line of code:
>>> x = []
[] is the empty list, x is a variable that points to the empty list, but x itself is not the empty list.
Consider the variable (x, in the above case) as a box, and 'the value' of the variable ([]) as the object inside the box.
PASS BY OBJECT REFERENCE (Case in python):
Here, "Object references are passed by value."
def append_one(li):
li.append(1)
x = [0]
append_one(x)
print x
Here, the statement x = [0] makes a variable x (box) that points towards the object [0].
On the function being called, a new box li is created. The contents of li are the SAME as the contents of the box x. Both the boxes contain the same object. That is, both the variables point to the same object in memory. Hence, any change to the object pointed at by li will also be reflected by the object pointed at by x.
In conclusion, the output of the above program will be:
[0, 1]
Note:
If the variable li is reassigned in the function, then li will point to a separate object in memory. x however, will continue pointing to the same object in memory it was pointing to earlier.
Example:
def append_one(li):
li = [0, 1]
x = [0]
append_one(x)
print x
The output of the program will be:
[0]
PASS BY REFERENCE:
The box from the calling function is passed on to the called function. Implicitly, the contents of the box (the value of the variable) is passed on to the called function. Hence, any change to the contents of the box in the called function will be reflected in the calling function.
PASS BY VALUE:
A new box is created in the called function, and copies of contents of the box from the calling function is stored into the new boxes.
You can not change an immutable object, like str or tuple, inside a function in Python, but you can do things like:
def foo(y):
y[0] = y[0]**2
x = [5]
foo(x)
print x[0] # prints 25
That is a weird way to go about it, however, unless you need to always square certain elements in an array.
Note that in Python, you can also return more than one value, making some of the use cases for pass by reference less important:
def foo(x, y):
return x**2, y**2
a = 2
b = 3
a, b = foo(a, b) # a == 4; b == 9
When you return values like that, they are being returned as a Tuple which is in turn unpacked.
edit:
Another way to think about this is that, while you can't explicitly pass variables by reference in Python, you can modify the properties of objects that were passed in. In my example (and others) you can modify members of the list that was passed in. You would not, however, be able to reassign the passed in variable entirely. For instance, see the following two pieces of code look like they might do something similar, but end up with different results:
def clear_a(x):
x = []
def clear_b(x):
while x: x.pop()
z = [1,2,3]
clear_a(z) # z will not be changed
clear_b(z) # z will be emptied
OK, I'll take a stab at this. Python passes by object reference, which is different from what you'd normally think of as "by reference" or "by value". Take this example:
def foo(x):
print x
bar = 'some value'
foo(bar)
So you're creating a string object with value 'some value' and "binding" it to a variable named bar. In C, that would be similar to bar being a pointer to 'some value'.
When you call foo(bar), you're not passing in bar itself. You're passing in bar's value: a pointer to 'some value'. At that point, there are two "pointers" to the same string object.
Now compare that to:
def foo(x):
x = 'another value'
print x
bar = 'some value'
foo(bar)
Here's where the difference lies. In the line:
x = 'another value'
you're not actually altering the contents of x. In fact, that's not even possible. Instead, you're creating a new string object with value 'another value'. That assignment operator? It isn't saying "overwrite the thing x is pointing at with the new value". It's saying "update x to point at the new object instead". After that line, there are two string objects: 'some value' (with bar pointing at it) and 'another value' (with x pointing at it).
This isn't clumsy. When you understand how it works, it's a beautifully elegant, efficient system.
Hope the following description sums it up well:
There are two things to consider here - variables and objects.
If you are passing a variable, then it's pass by value, which means the changes made to the variable within the function are local to that function and hence won't be reflected globally. This is more of a 'C' like behavior.
Example:
def changeval( myvar ):
myvar = 20;
print "values inside the function: ", myvar
return
myvar = 10;
changeval( myvar );
print "values outside the function: ", myvar
O/P:
values inside the function: 20
values outside the function: 10
If you are passing the variables packed inside a mutable object, like a list, then the changes made to the object are reflected globally as long as the object is not re-assigned.
Example:
def changelist( mylist ):
mylist2=['a'];
mylist.append(mylist2);
print "values inside the function: ", mylist
return
mylist = [1,2,3];
changelist( mylist );
print "values outside the function: ", mylist
O/P:
values inside the function: [1, 2, 3, ['a']]
values outside the function: [1, 2, 3, ['a']]
Now consider the case where the object is re-assigned. In this case, the object refers to a new memory location which is local to the function in which this happens and hence not reflected globally.
Example:
def changelist( mylist ):
mylist=['a'];
print "values inside the function: ", mylist
return
mylist = [1,2,3];
changelist( mylist );
print "values outside the function: ", mylist
O/P:
values inside the function: ['a']
values outside the function: [1, 2, 3]
Python is neither pass-by-value nor pass-by-reference. It's more of "object references are passed by value" as described here:
Here's why it's not pass-by-value. Because
def append(list):
list.append(1)
list = [0]
reassign(list)
append(list)
returns [0,1] showing that some kind of reference was clearly passed as pass-by-value does not allow a function to alter the parent scope at all.
Looks like pass-by-reference then, hu? Nope.
Here's why it's not pass-by-reference. Because
def reassign(list):
list = [0, 1]
list = [0]
reassign(list)
print list
returns [0] showing that the original reference was destroyed when list was reassigned. pass-by-reference would have returned [0,1].
For more information look here:
If you want your function to not manipulate outside scope, you need to make a copy of the input parameters that creates a new object.
from copy import copy
def append(list):
list2 = copy(list)
list2.append(1)
print list2
list = [0]
append(list)
print list
Technically python do not pass arguments by value: all by reference. But ... since python has two types of objects: immutable and mutable, here is what happens:
Immutable arguments are effectively passed by value: string, integer, tuple are all immutable object types. While they are technically "passed by reference" (like all parameters), since you can't change them in-place inside the function it looks/behaves as if it is passed by value.
Mutable arguments are effectively passed by reference: lists or dictionaries are passed by its pointers. Any in-place change inside the function like (append or del) will affect the original object.
This is how Python is designed: no copies and all are passed by reference. You can explicitly pass a copy.
def sort(array):
# do sort
return array
data = [1, 2, 3]
sort(data[:]) # here you passed a copy
Last point I would like to mention which is a function has its own scope.
def do_any_stuff_to_these_objects(a, b):
a = a * 2
del b['last_name']
number = 1 # immutable
hashmap = {'first_name' : 'john', 'last_name': 'legend'} # mutable
do_any_stuff_to_these_objects(number, hashmap)
print(number) # 1 , oh it should be 2 ! no a is changed inisde the function scope
print(hashmap) # {'first_name': 'john'}
So this is a little bit of a subtle point, because while Python only passes variables by value, every variable in Python is a reference. If you want to be able to change your values with a function call, what you need is a mutable object. For example:
l = [0]
def set_3(x):
x[0] = 3
set_3(l)
print(l[0])
In the above code, the function modifies the contents of a List object (which is mutable), and so the output is 3 instead of 0.
I write this answer only to illustrate what 'by value' means in Python. The above code is bad style, and if you really want to mutate your values you should write a class and call methods within that class, as MPX suggests.
Consider that the variable is a box and the value it points to is the "thing" inside the box:
1. Pass by reference : function shares the same box and thereby the thing inside also.
2. Pass by value : function creates a new box, a replica of the old one, including a copy of whatever thing is inside it. Eg. Java - functions create a copy of the box and the thing inside it which can be: a primitive / a reference to an object. (note that the copied reference in the new box and the original both still point to the same object, here the reference IS the thing inside the box, not the object it is pointing to)
3. Pass by object-reference: the function creates a box, but it encloses the same thing the initial box was enclosing. So in Python:
a) if the thing inside said box is mutable, changes made will reflect back in the original box (eg. lists)
b) if the thing is immutable (like python strings and numeric types), then the box inside the function will hold the same thing UNTIL you try to change its value. Once changed, the thing in the function's box is a totally new thing compared to the original one. Hence id() for that box will now give the identity of the new thing it encloses.
The answer given is
def set_4(x):
y = []
for i in x:
y.append(i)
y[0] = 4
return y
and
l = [0]
def set_3(x):
x[0] = 3
set_3(l)
print(l[0])
which is the best answer so far as it does what it says in the question. However,it does seem a very clumsy way compared to VB or Pascal.Is it the best method we have?
Not only is it clumsy, it involves mutating the original parameter in some way manually eg by changing the original parameter to a list: or copying it to another list rather than just saying: "use this parameter as a value " or "use this one as a reference". Could the simple answer be there is no reserved word for this but these are great work arounds?
class demoClass:
x = 4
y = 3
foo1 = demoClass()
foo1.x = 2
foo2 = demoClass()
foo2.y = 5
def mySquare(myObj):
myObj.x = myObj.x**2
myObj.y = myObj.y**2
print('foo1.x =', foo1.x)
print('foo1.y =', foo1.y)
print('foo2.x =', foo2.x)
print('foo2.y =', foo2.y)
mySquare(foo1)
mySquare(foo2)
print('After square:')
print('foo1.x =', foo1.x)
print('foo1.y =', foo1.y)
print('foo2.x =', foo2.x)
print('foo2.y =', foo2.y)
In Python the passing by reference or by value has to do with what are the actual objects you are passing.So,if you are passing a list for example,then you actually make this pass by reference,since the list is a mutable object.Thus,you are passing a pointer to the function and you can modify the object (list) in the function body.
When you are passing a string,this passing is done by value,so a new string object is being created and when the function terminates it is destroyed.
So it all has to do with mutable and immutable objects.
Python already call by ref..
let's take example:
def foo(var):
print(hex(id(var)))
x = 1 # any value
print(hex(id(x))) # I think the id() give the ref...
foo(x)
OutPut
0x50d43700 #with you might give another hex number deppend on your memory
0x50d43700

How to implement a static counter in python

I am calling a method and I need a static counter within this method. It's required to parse the elements of the list. The counter will tell which position of the list to lookup at.
For e.g
static_var_with_position = 0
noib_list = [3, 2, 2, 2, 2, 1, 2, 2]
def foo(orig_output, NOB):
# tried two ways
#static_var_with_position += 1 # doesn't work
#global static_var_with_position
#static_var_with_position += 1 # doesn't work either
bit_required = noib_list[static_var_with_position]
converted_output = convert_output(orig_output, NOB, bit_required)
The static_var_with_position value is never incremented. I have commented the two ways I tried to increment value.
In c++ its piece of cake, but I couldn't find anything similar in python so far. Any help will be appreciated :)
Thanks!
Instead of using a global/static counter variable, you could use an iterator:
iterator = iter(noib_list)
def foo(orig_output, NOB):
bit_required = next(iterator)
converted_output = convert_output(orig_output, NOB, bit_required)
The iterator will automatically keep track which is the next element internally.
When the iterator is exhausted (i.e. when you reached the end of the list), next will raise a StopIteration error, so it you do not know when the end is reached, you can use bit_required = next(iterator, None) instead; then just test whether the value is None and you know that the list is exhausted (or just use try/except).
Following this example, you could do the same with your counter :
def inc() :
global global_cpt
global_cpt +=1
print global_cpt
if __name__ == '__main__' :
global_cpt = 0
inc()
inc()
inc()
will print
> 1
> 2
> 3
I don't actually advocate doing this in your case, but it's a little-known hack for creating a "static" variable within a function: put it as a parameter with a mutable default value! You can modify it within the function and it will hold until the next function call, as long as the caller doesn't pass a value for it.
def foo(value=[0]):
value[0] += 1
print(value[0])
>>> foo()
1
>>> foo()
2
>>> foo()
3
>>> foo([906])
907
>>> foo()
4

Alternate to global variable in Python

I am implementing a recursive function in which I need to remember a global value. I will decrement this value in every recursive call and want it to reflect in other recursive calls also.
Here's a way I've done it.
First way:
global a
a = 3
def foo():
global a
if a == 1:
print 1
return None
print a
a -= 1 # This new 'a' should be available in the next call to foo()
foo()
The output:
3
2
1
But I want to use another way because my professor says global variables are dangerous and one should avoid using them.
Also I am not simply passing the variable 'a' as argument because 'a' in my actual code is just to keep track of some numbers, that is to track the numbering of nodes I am visiting first to last. So, I don't want to make my program complex by introducing 'a' as argument in every call.
Please suggest me whatever is the best programming practice to solve the above problem.
Don't use a global; just make a a parameter to the function:
def foo(a):
print a
if a == 1:
return None
foo(a-1)
foo(3)
Try this :
Use a parameter instead of a global variable.
Example code
a = 3
def foo(param):
if param == 1:
print 1
return None
print param
foo(param - 1)
foo(a)

Categories

Resources