5 ms·
That's one way of thinking about it, and it works well for Python. Here's another one, and the way I think about it: def _loop(i): functions.appen
by BoppreH 4y ago
That's one way of thinking about it, and it works well for Python.
Here's another one, and the way I think about it:
def _loop(i):
functions.append(lambda: print(f"Hello {i}"))
_loop(0)
_loop(1)
_loop(2)
print(i)
# Error!
I highly prefer lexical scoping, where the variables are bound to the block they were declared in, like Javascript's `let` vs the old `var`. This avoids shadowing and general namespace pollution.
I know it's not how Python operates, but I think it's how it should. Though I'd argue the syntactical similarities between functions and loops nudge users towards this second model.
- pdonis 4y agoNote 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 in In 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.
- dkmar 4y agoWow. When I saw the original for-loop example I was like "ok not a huge deal, doesn't surprise me too much really". But rewritten in the comprehension form I'm like wtf
- gshulegaard 4y agoWell put. I too prefer the reasonability of lexical scoping. Although I must confess I am not well versed enough to quite grasp how what Python does _isn't_ conforming to lexical scope. Since I was confused I did some internet searching and found this: https://stackoverflow.com/questions/51604346/does-python-scoping-rule-fits-the-definition-of-lexical-scoping https://stackoverflow.com/questions/51604346/does-python-sco... Which I think suggests the thing you are finding confusing isn't lexical scoping in Python, but rather the environment mutability. Ultimately, I think I better understand what you are highlighting and can't say I entirely disagree. I just personally find how it is today ergonomic, but that could also just be bias as I am fairly comfortable in Python (warts and all).
- randomswede 4y agoI woudl say taht Python has scoping, yes, but it does not have lexical scoping in any sense of "lexical scoping" that I am aware of. If it did, the code below would not actually work ,as the outside-the-loop print would be trying to access a variable not available in the lexical scope of "the function body", as it is defined and established within the lexical scope of "the loop". So, at least in my book, no, Python has "global scope", "function scope" and probably one or two more scopes (I think there's a "class scope" as well). Here's some code in Python, and some equivalent code in Go. def foo(a_list): print(f"list is {len(a_list} elements") for element in a_list: print(element) print(element) And here's the equivalent Go code: func foo(aList []int) { // Let's use ints... fmt.Printf("list is %d elements\n", len(aList)) var element int // Notice this declaration! This is ensuring that element is declared outside the lexical scope of the for loop for _, element = range aList { fmt.Println(element) } fmt.Println(element) }
- gshulegaard 4y agoI think you are confusing type of scoping (lexical/static, dynamic) with scoping levels. Python has lexical scoping, but it does _not_ have block level scoping. https://en.wikipedia.org/wiki/Scope_(computer_science)#Block_scope https://en.wikipedia.org/wiki/Scope_(computer_science)#Block... Which, now that I have done enough reading I think crystallizes the confusing thing for others being highlighted here (for me). Depending on preference, the lack of block scoping can be surprising for someone. Which also explains my bias, I started with Python which probably plays a large part in why I find function level scoping without block scoping ergonomic.