Effective Python Note Item 20 Use None and the documentation string when specifying dynamic default arguments

This is a memo of O'Reilly Japan's book effective python. https://www.oreilly.co.jp/books/9784873117560/ P47~50

Be careful when putting dynamic arguments to function arguments

For example, when defining a function that outputs login time and logging message at the same time

from datetime import datetime
from time import sleep

def log(massage, when=datetime.now()):
    print('%s: %s' % (when, massage))

Now, if you give a message to the log () function as an argument, that time will be output at the same time as the message ... When I actually try it

log('Hi there!')
sleep(1)
log('Hi there!')

>>>
2016-06-14 00:22:04.668549: Hi there!
2016-06-14 00:22:04.668549: Hi there!

The time is the same even though I put sleep () for 1s. Furthermore, this time is not the time when the message is input, but the time when the log () function is defined. Why is this happening?

The default argument is evaluated only once when the module is called

That is, datetime.now () is evaluated only for the moment when the function log () is defined, and all the values after that are the same. ** To resolve this, set the default argument to None **

def log2(massage, when=None):
    """Lof a massage with a timestamp.
    
    Args:
        massage: Massage to print.
        when: datetime of when the massage occurred.
            Dafaults to the present time.
    """
    when = datetime.now() if when is None else when
    print('%s: %s' % (when, massage))

The None attribute is given to the argument when in advance, and when is defined and called each time in the function. At this time, it is easier to understand if you write the behavior in the documentation.

log2('Hi there!')
sleep(1)
log2('Hi there!')

>>>
2016-06-14 00:34:21.793073: Hi there!
2016-06-14 00:34:22.794493: Hi there!

The time stamp is now correct.

Another example! For functions that load JSON data

def decode(data, default={}):
    try:
        return json.loads(data)
    except ValueError:
        return default

Whenever I try to call the decoded data as JSON data using the decode () function

foo = decode('bad data')
foo ['sruff'] = 5
bar = decode('also bad')
bar['meep'] = 1
print('Foo:', foo)
print('Bar:', bar)

>>>
Foo: {'sruff': 5, 'meep': 1}
Bar: {'sruff': 5, 'meep': 1}

In this case, it is assumed that each of Foo and Bar will be a dictionary of one key and value, but like the above dynamic argument, the default dictionary will be fixed when decode () is defined. As a result, even if you call decode () multiple times, the dictionary inside will always inherit one.

This is also solved by putting None in the default (behavior is written in the documentation)

def decode2(data, default=None):
    """Load JSON data from a string.
    Args:
        data: JSON data to decode
        default: Value to return if decoding fails.
            Dafaults to an empty dictionary.
    """
    if default is None:
        default = {}
    try:
        return json.loads(data)
    except ValueError:
        return default

foo = decode2('bad data')
foo ['sruff'] = 5
bar = decode2('also bad')
bar['meep'] = 1
print('Foo:', foo)
print('Bar:', bar)

>>>
Foo: {'sruff': 5}
Bar: {'meep': 1}

** Summary ** --Function defaults are read only once --Dynamic argument is None

Recommended Posts

Effective Python Note Item 20 Use None and the documentation string when specifying dynamic default arguments
Effective Python Note Item 17 Respect for certainty when using iterators for arguments
Effective Python Memo Item 18 Use variable-length positional arguments to make the appearance cleaner
Be careful when specifying the default argument value in Python3 series
Effective Python memo Item 7 Use list comprehension instead of map and filter
[Python] Use and and or when creating variables
Use python on Raspberry Pi 3 and turn on the LED when it gets dark!
When do python functions affect the caller's arguments?
Specifying the range of ruby and python arrays
Python Note: When assigning a value to a string
json.dumping None in python returns the string null
The timing when the value of the default argument is evaluated is different between Ruby and Python.
When will the default arguments be bound in python? When variables are bound in closure lazy evaluation.