The Python terminology and wording is quite suspicious.
TLTR
I read an article about python lazy evaluation and summarized it.
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 **.
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.
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 `.
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.
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.
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)
.
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.
It's hard to understand, so I'll post another one.
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.
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.
It seems that you should use the library. This time it is omitted.
from functools import partial
from operator import mul
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>
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.
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