When will the default arguments be bound in python? When variables are bound in closure lazy evaluation.

Caution

The Python terminology and wording is quite suspicious.

TLTR

Motivation

I read an article about python lazy evaluation and summarized it.

Example 1 When are the default arguments of a function evaluated?

Python can set default arguments. However, ** the default arguments of the function are evaluated when defining the function. Not at runtime. It will be an unexpected pitfall if you do not know the specification of **.

Sometimes the default argument = None is good

Define an append_to function that just adds the passed arguments to the list. It has an empty list to by default.

Append argument to add to list_to function


>>> def append_to(element, to=[]):
...     to.append(element)
...     return to
...

Let's add 12,42. Because there is an argument to = [] [12] [42] It seems to be.

>>> my_list = append_to(12)
>>> print(my_list)
[12]
>>> my_other_list = append_to(42)
>>> print(my_other_list)
[12, 42]

It's not what you expected. What does that mean.

Cause

The default argument to is defined as to = []. We will update the value one after another here.

If to = [] is evaluated in the process of becoming to = []-> to = [12]-> to = [12,42],

to = []-> to = [12]-> to = []-> to = [42], which seems to give the desired result, but ** the default argument is a function It will be evaluated at the time of definition. ** **

In other words, to is to = [] when defining a function, but ** does not do to = [] every time it is called **, so when ʻappent_to (42), to = [12] It means that it remains `.

solution

What to do instead It seems that to = None should be done.

It feels a bit redundant. I don't know what to do.

def append_to(element, to=None):
    if to is None:
        to = []
    to.append(element)
    return to


my_list = append_to(12)
print(my_list)

my_other_list = append_to(42)
print(my_other_list)

[12]
[42]

I got the desired result.

Example 2 Closure lazy evaluation

Python can do lazy evaluation. Lazy evaluation, roughly speaking, means evaluating when needed.

If you are a beginner like me who is learning Python normally, this is "I know! This is the one I saw in the seminar".

If you're going to write medium to large code, it's inevitable to design the default arguments for your function.

However, there is such a Python specification.

** Variable bindings in closures are lazy **

Let's look at a code example that uses so-called closures, where values are stored inside a function.

The goal is code that returns a list with the contents of the list multiplied by the value of the argument.

A script that returns a list with the contents of the list multiplied by the value of the argument


functions = []
for n in [1, 2, 3]:
    def func(x):
        return n * x
    functions.append(func)

# You would expect this to print [2, 4, 6]
print('{}'.format(str([function(2) for function in functions])))

It seems that [2, 4, 6]([1 * 2, 2 * 2, 3 * 2]) will be output, but ...

python


[6, 6, 6]

Was output.

Cause

Python closures are lazy evaluated. The value of the variable used by the closure is also referenced at the time it is called.

Therefore, n is not fixed when the closure is called in the for statement, and it is stored in functions in the state of n * x. So to speak, [n * x, n * x, n * x] Therefore, when it is executed by the print statement, the last state of n, n == 3, is referenced, so[6,6,6]([3 * 2,3 * 2,3 * 2) ], X = 2).

Try to debug

Let's use the debugger to see the order of updating the values of the for statement and the print statement.

Since the value of the variable n has been updated by the iteration to already be 3, the function is evaluated by print all the time n == 3. You can see from print that n == 3 is referenced 3 times.

2020-05-192.gif

It's hard to understand, so I'll post another one.

2020-05-17.gif

solution

Change the closure argument from x to x, n = n. The problem is solved by passing the value of n, that is, passing the value of n to the functions that store the closure each time it is iterated by a for statement.

To do this, put the value of n in the argument, such as def func (x, n = n):, and make it take the form of reassignment. This allows closures to update the value of n from the specification of holding the value within that function.

A script that returns a list with the contents of the list multiplied by the value of the argument



functions = []
for n in [1, 2, 3]:
    def func(x, n=n):# n=Added n.
    # def func(x):
        return n * x
    functions.append(func)


print('{}'.format(str([function(2) for function in functions])))


Output result


[2,4,6]

Let's take a look at the debugger.

2020-05-193.gif

I'm wondering if the behavior of func could be expressed well with the debugger, but I was able to make it behave as I expected.

Other solutions

It seems that you should use the library. This time it is omitted.

from functools import partial
from operator import mul

Summary

Python does lazy evaluation. This is a bit confusing when you're doing regular procedural programming. It's a function brought in from a functional language, so it's a bit quirky.

I'm not sure how to use the expression variable binding (assignment in functional languages) properly in Python. </ del>

Postscript 2020/05/19

There was a point in the comment.

I wrote in Example 1 that Python's default arguments are lazy evaluated. But this was an incorrect understanding. The default Python argument is a specification that is bound when a function is declared, rather than being lazy and bound. It seems more correct to behave in a non-intuitive manner as in Example 1.

References

https://ja.coder.work/so/python/1087272 https://python-guideja.readthedocs.io/ja/latest/writing/gotchas.html https://stackoverflow.com/questions/36463498/late-binding-python-closures

Recommended Posts

When will the default arguments be bound in python? When variables are bound in closure lazy evaluation.
Be careful when specifying the default argument value in Python3 series
When will the default arguments be bound in python? When variables are bound in closure lazy evaluation.
Python: Class and instance variables
Python class variables and instance variables
Landmines hidden in Python class variables
When will mmap (2) files be updated? (2)
When will mmap (2) files be updated? (1)
[Python] Be careful when using print
[Python] Inherit a class with class variables
[Python] Use and and or when creating variables
Predict when the ISS will be visible
Master the type in Python? (When should type check be done)
Things to watch out for when using default arguments in Python
python: Use the variables defined in the string format as they are
Precautions when giving default values to arguments in Python function definitions
Predict when the ISS will be visible
[Python] I want to know the variables in the function when an error occurs!
If you add sudo on ubuntu, it will be called the default python.
You will be an engineer in 100 days --Day 29 --Python --Basics of the Python language 5
You will be an engineer in 100 days --Day 33 --Python --Basics of the Python language 8
You will be an engineer in 100 days --Day 26 --Python --Basics of the Python language 3
Execution order when multiple context managers are specified in the Python with statement
You will be an engineer in 100 days --Day 32 --Python --Basics of the Python language 7
You will be an engineer in 100 days --Day 28 --Python --Basics of the Python language 4
Check if the characters are similar in Python
When do python functions affect the caller's arguments?
Use pydantic when reading environment variables in Python
What to do when is not in the sudoers file.This incident will be reported.
Python note: When the pip command cannot be used
Initial settings when using the foursquare API in python
Get environment variables in Python, otherwise set default values
Using the LibreOffice app in Python (1) Where are the macros?
Python default arguments are not initialized on every call
Effective Python Note Item 20 Use None and the documentation string when specifying dynamic default arguments