Python advanced details of the function default parameter of

  • 2020-06-01 10:15:31
  • OfStack

1. Default parameters

To simplify function calls, python provides a default parameter mechanism:


def pow(x, n = 2):

 r = 1
 while n > 0:
  r *= x
  n -= 1
 return r

In this way, when calling the pow function, the last parameter can be omitted without writing:


print(pow(5)) # output: 25

When defining a function with default parameters, note the following:

Required parameters must be in front, default parameters after;

What parameters are set as the default parameters? 1 generally, set the parameter values that vary little as the default parameter.

python standard library practices

python built-in functions:


print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)

As you can see from the function signature, a printed statement that USES a simple call like print('hello python') actually passes in a lot of default values, and the default parameters make it very easy to call the function.

2. Default parameter of error

To cite an official classic example address:


def bad_append(new_item, a_list=[]):
 a_list.append(new_item)
 return a_list

print(bad_append('1'))
print(bad_append('2'))

This example does not print as expected:


['1']
['2']

Instead, it prints:


['1']
['1', '2']

The problem is not with the default parameters, but with our understanding of the initialization of the default parameters.

3. Initialization of default parameters

In fact, the value of the default parameter is only evaluated once at the time of definition, so each time the function is called with the default parameter, the default parameter value is the same.

Let's illustrate with an intuitive example:


import datetime as dt
from time import sleep


def log_time(msg, time=dt.datetime.now()):

 sleep(1) #  Thread to suspend 1 seconds 
 print("%s: %s" % (time.isoformat(), msg))

log_time('msg 1')
log_time('msg 2')
log_time('msg 3')

Run this program and the output is:


2017-05-17T12:23:46.327258: msg 1
2017-05-17T12:23:46.327258: msg 2
2017-05-17T12:23:46.327258: msg 3

Even if sleep(1) is used to pause the thread for a second, it eliminates the need for the program to execute quickly. The three calls in the output print out the same time, that is, the value of the default parameter time is the same in the three calls.

While the above example may not be enough, here's how to look at the memory address of the default parameter.

First you need to understand the built-in function id(object) :

id(object)
Return identity of of an object This is an to be lifetime constant during

lifetime Two objects with non-overlapping lifetimes may have the id() value.

CPython implementation detail: This is the address of the object in memory.

The id(object) function returns the unique identity of an object. This identifier is an integer that is guaranteed to be unique and unchanging throughout the lifetime of the object. In overlapping lifecycles, two objects may have the same id value.
In the CPython interpreter implementation, the value of id(object) is the memory address of the object.

The following example USES the id(object) function to make the point clear:


def bad_append(new_item, a_list=[]):
 
 print('address of a_list:', id(a_list))
 a_list.append(new_item)
 return a_list

print(bad_append('1'))
print(bad_append('2'))

output:


address of a_list: 31128072
['1']
address of a_list: 31128072
['1', '2']

bad_append is called twice, and the default parameter a_list has the same address.

Moreover, a_list is a mutable object. Adding new elements using the append method will not cause the re-creation of list objects or the redistribution of addresses. This' happens' to modify the object at the address pointed to by the default parameter, and when the address is used again on the next call, you can see the previous change.

It is not surprising, then, that the above outputs appear, since they all point to the same memory address.

4. Variable and immutable default parameters

When default parameters point to mutable type objects and immutable type objects, they behave differently.

Variable default parameters behave like example 1.

Immutable default parameters

Let's start with an example:


print(pow(5)) # output: 25
0

Output:


print(pow(5)) # output: 25
1

Obviously, the value of the default parameter i on the second call will not be affected by the first call. Since i points to an immutable object, the operation on i will cause a memory reallocation and the object will be recreated. Then the name i points to another address after i += 1 in the function. According to the default parameter rules, the next call to i will be to the same address that the function was defined at, and the value of 1 has not been changed.

In fact, the discussion of mutable default parameters and immutable default parameters is of little value here, just as the so-called value pass or reference pass 1 in other languages does not only affect the default parameters.

5. Best practices

Multiple invocations of immutable default parameters do not make any difference, and multiple invocations of mutable default parameters do not produce the desired results. When using mutable default parameters, instead of initializing the function once at function definition, you should initialize it every time you call it.

The best practice is to specify a variable default parameter value of None when defining a function, and rebind the default parameter value within the function body. Here is an application of the two mutable default parameter example best practices above:


print(pow(5)) # output: 25
2

import datetime as dt
from time import sleep

def log_time(msg, time = None):

 if time is None:
  time = dt.datetime.now()

 sleep(1)
 print("%s: %s" % (time.isoformat(), msg))

log_time('msg 1')
log_time('msg 2')
log_time('msg 3')

Related articles: