Python functions I- Argument passing
I. Default arguments:
The following include examples from python.org tutorial on python and my attempt to understand and explain what is going on. The python interpreter evaluates the default arguments only once when it reads the function definition before the function is actually called. If the function is called again, the default arguments will not be evaluated again. There are many reasons quoted for this and you can read the link to a relevant discussion at the end for more details. Mainly-
(i) A function is an object in python. It is evaluated at its definition. Default arguments are like member data.
(ii) It makes sense to bind default arguments at function definition rather than at function execution. (iii) It is more inexpensive and cost efficient in terms of operation.
However a newbie or a naive programmer can run into trouble when some unexpected scenarios come up.
i) A function call as a default argument:
def fn(a=someobject.somefunc())
Let us see what happens when the function returns a value that changes at runtime every time you call it. For example time.time().
def fn(a=time.time()):
print a
>>fn()
12:00:36
>>fn()
12:00:36
This would print the same value every time the function is called because the default argument is evaluated only once.
ii) Mutable default argument being modified inside the function:
def fn(L=[]):
L.append(1)
Lets look at this in more detail.
def fn(a=1): #immutable default arg
print a
return None
>>>fn()
1
>>>fn(3)
3
>>>fn(a=2)
2
-----------------------------------------------
def fn(L=[]): #mutable list as a default arg
print L
L.append(1)
print L
return None
>>>fn()
[]
[1]
>>fn()
[1]
[1,1]
(iii)Workaround for the mutable default arg case:
If you want the list to be initialized to None every time the function is called, here is what you need to do.
def fn(L=None):
if L is None:
L = []
print L
L.append(1)
print L
return None
The statement - "if L is None" forces Python to re-evaluate the default value of L and the second statement "L = []" sets it to an initialized value. The idea is that if you want to use mutable default arguments, always set your default argument to an immutable value (like None)and then set it to the initial mutable value inside the function so on every function call that is the value it has.
You would also get the same result if you did the following.
def fn(L=None):
L = []
print L
L.append(1)
print L
return None
Here is a helpful link on stackoverflow for mutable default arguments .
II. Passing by reference:
def func1 ():
print "Inside function func1"
return None
def func2 (func1):
print "Inside function func2"
return None
>>func2()
Inside function func2
Note that func1 is not executed until it is explicitly called inside func2. See below.
def func2 (func1):
print "Inside function func2"
a= func1()
return None
Now func1 will be called.
>>func2()
Inside function func2
Inside function func1
III. Keyword Arguments
The function arguments are expected to be in the following sequence: positional arguments, keyword arguments. Let's say there is a function which is defined as follows:
def fn(a,b):
print a,b
return None
>>>fn(1,2)
1 2
>>>fn(2,1)
2 1
>>>D = {'a':1}
>>>tpl = (1,)
>>>fn(b=2,**D)
1 2
>>>fn(b=2,*tpl)
1 2
>>>fn(2,**D)
Error: multiple values passed for argument a (If we do not explicitly mention b=2 while passing arguments, since the first argument is 'a' in fn, it is going to expect the first val to be passed to a instead of b and then when it encounters the second argument as the unpacked dict **D (which has 'a':1), it will give error because this is the second time the value for a parameter is passed.
>>>fn(**D,2)
Will give invalid syntax since keyword arguments can only be passed in the end.