Re:Understanding a Python Closure Oddity
by peterhull90 on Monday June 17, 2024 @10:43AM
(#40706103)
You can put an extra 'level' in, to make both options return the same 0,1...9
Is there a neater way?
[ Parent
| on HN ]
Yes. The usual way to do this is to bind them as defaults to an argument, for example:
def loop():
for number in range(10):
def func_w_closure(_num=number):
return _num
yield func_w_closure
This works because default arguments in python are evaluated exactly once, at function definition time. So it's a way of effectively copying the ``number`` out of the closure[1] and into the function definition.[1] side note, closures in python are always late-binding, which is what causes the behavior in OP
Does this work for user defined classes (objects) as well?
Yes, and by the same mechanism and for the same reason.
I’ve never thought that leaking this type of implementation detail into the return value (and return type!) was a nice solution. I like the double closure better, and one can shorten it a bit with a lambda.
For those who prefer a functional style, functools.partial can also solve this problem.
(I use Python, and I like a lot of things about Python, but I don’t like its scoping rules at all, nor do I like the way that Python’s closures work. I would use a double lambda.)
Another way to use function arguments to fix it is to put another level of function in:
def loop():
for number in range(10):
def make_closure(n):
def closure():
return n
return closure
yield make_closure(number)I would have just done something like
``` def loop(): for number in range(10): fixed_number = number def inner(): return fixed_number yield inner ```
Correctly formatted (two spaces preceding each line, one blank line before the first code line, no extra lines needed between code lines):
def loop():
for number in range(10):
fixed_number = number
def inner():
return fixed_number
yield inner
The output: eagerly = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
lazily = [9, 9, 9, 9, 9, 9, 9, 9, 9, 9]
It doesn't work because of the way Python's variables are scoped. Your fixed_number variable is still shared across all instances of inner. Python doesn't have any sort of block scoping like you seem to think it has.