"Structural pattern matching" for Python, part 2
"Structural pattern matching" for Python, part 2
Posted Sep 9, 2020 14:37 UTC (Wed) by mathstuf (subscriber, #69389)In reply to: "Structural pattern matching" for Python, part 2 by milesrout
Parent article: "Structural pattern matching" for Python, part 2
>>> d = []
>>> f = lambda x=d: print(x)
>>> f()
[]
>>> d.append(3)
>>> f()
[3]
Unless I'm missing what you're trying to capture here?
Posted Sep 10, 2020 2:11 UTC (Thu)
by milesrout (subscriber, #126894)
[Link] (1 responses)
>>> a = 0
f captures the *name* 'a', which refers to the immutable value 0. g has an optional parameter, the default value of which is the value of a i.e. the immutable value 0. Like any Python function, g will store internally references to the default values for its parameters. It can't 'capture' the variable a because you could put *any* expression as the default value for g's parameter, and that expression is evaluated *when the function is defined*, with the resulting value stored inside the closure object somewhere.
When f is called, it prints a i.e. the value referred to by the name 'a' that f captured. If 'a' has changed then what f prints will change. When g is called, it prints the value of its parameter or if it isn't given one, the default value, which is zero. The 'a' in f refers not to the value that 'a' had when f was created, but to the name 'a' itself in the definition environment of f.
When a is rebound to 1, it doesn't change the value that a was originally bound to. 'a = 1' is an operation on the name 'a', not an operation on the value pointed to by a. We reassign 'a' so that it refers to a different value, but we don't modify the value that 'a' previously pointed to. So when f() is called a second time, it prints 1 (the new value pointed to by a) while when g() is called a second time it prints 0 (the value you can think of as being pointed to by some 'g.__internal_stuff__.parameters[0].default_value' attribute).
h and i actually work exactly the same way, but with one crucial difference: the value that the name 'b' refers to is a list and is thus mutable. So when we do the 'b.append(3)' operation we are not changing the name 'b'! We're modifying the value *pointed to by b*. When we defined i, we evaluated the expression "b" and pointed some internal "this is the default value of my parameter" field of the closure object at the result of that evaluation. Evaluating the expression "b" results in that list object, the list object pointed to by the name 'b'.
So when we call h(), it prints the value pointed to by the name 'b'. When we call i(), it prints the value of parameter, if it exists, or the value pointed to by some internal 'default' reference for that parameter. That default reference hasn't been changed, it still points to the same object. However that object itself has been changed by the call to append. We only ever created *one* list object, and there are two references to it.
The 'mutable default value' issue for Python functions basically has nothing to do with variable capture at all. It's present even for normal function definitions:
def foo(x=[]):
>>> foo()
The main problem that the variable capture issue causes happens even with immutable values, and in fact is primarily an immutable value issue. The first time I encountered it was with a GUI library. I was doing something like this:
>>> for i in range(5):
but of course the problem with this is that all the buttons will make the last button red when clicked. That's not because of a mutable value: the value pointed to by i doesn't change. The problem here is that a for loop assigns to the iteration variable. That code is the same as writing
>>> i = 0
And there it's more clear what's happening vis a vis name capture. The solution is of course to instead write buttons[i].on_click(lambda i=i: buttons[i].colour = RED).
Posted Sep 10, 2020 13:21 UTC (Thu)
by mathstuf (subscriber, #69389)
[Link]
"Structural pattern matching" for Python, part 2
>>> b = []
>>> f = lambda: print(a)
>>> g = lambda a=a: print(a)
>>> h = lambda: print(b)
>>> i = lambda b=b: print(b)
>>> (f(), g(), h(), i())
0
0
[]
[]
>>> a = 1; c.append(3)
>>> (f(), g(), h(), i())
1
0
[3]
[3]
x.append(1)
print(x)
[1]
>>> foo()
[1, 1]
>>> buttons[i].on_click(lambda: buttons[i].colour = RED)
>>> buttons[i].on_click(lambda: buttons[i].colour = RED)
>>> i = 1
>>> buttons[i].on_click(lambda: buttons[i].colour = RED)
etc.
"Structural pattern matching" for Python, part 2