Note that the way you write this is the best way to form closures in Python: form them inside a function with the closure variables as function parameters. This forces the closure to capture the current value of the closure variables when the closure is formed. Note that you could put the calls to the _loop function in a for loop and things would still work:
>>> functions = []
>>> def _loop(i):
... functions.append(lambda: print(f"Hello {i}"))
...
>>> for i in range(10):
... _loop(i)
...
>>> for f in functions:
... f()
...
Hello 0
Hello 1
Hello 2
Hello 3
Hello 4
Hello 5
Hello 6
Hello 7
Hello 8
Hello 9
> lexical scoping, where the variables are bound to the block they were declared inIn Python that would only work if a "block" included comprehensions. For example:
>>> functions = [lambda: print(f"Hello {i}") for i in range(10)]
>>> for f in functions:
... f()
...
Hello 9
Hello 9
Hello 9
Hello 9
Hello 9
Hello 9
Hello 9
Hello 9
Hello 9
Hello 9
You could fix this by defining a _loop function as above and forming the closure inside it; but changing variable scoping to be lexical in "blocks" wouldn't fix this case unless the list comprehension itself counted as a "block", which is not how Python defines blocks.