sloppy as fuck but mildly informative
why declare variables as nonlocal though is python a statically typed language now
Because Python's perverse scoping rules take assignment to any variable not explicitly declared as
nonlocal
to be declaration of an implicit local variable in the current (in this case, nested) function. This is how Python people talk:
Python:
>>> def make_counter():
... count = 0
... def counter():
... nonlocal count
... count += 1
... return counter
...
>>> counter = make_counter()
>>> counter()
1
>>> counter()
2
>>> counter()
3
In this example, count holds a reference to an integer value, which is immutable. To update the value of count, you use a nonlocal statement that tells Python you want to reuse the variable from the non-local scope.
When your variable points to a mutable object, you can modify the variable’s value in place:
Python:
>>> def make_appender():
... items = []
... def appender(new_item):
... items.append(new_item)
... return items
... return appender
...
>>> appender = make_appender()
>>> appender("First item")
['First item']
>>> appender("Second item")
['First item', 'Second item']
>>> appender("Third item")
['First item', 'Second item', 'Third item']
Use of a
nonlocal
declaration has nothing to do with whether the object bound to the nonlocal variable is mutable or not, it depends on whether you're assigning a new object to the variable or not. The second example doesn't need a
nonlocal
declaration because it's mutating the list that's already bound, but if it were binding a different list to the variable it would need a
nonlocal
declaration to avoid creating a local variable that masks the nonlocal. This confusion between variables and values is pervasive in the Python userbase.
Speaking of this affecting all Python example code, I've examined the source code for
threading.local
and found that the post from guy with line noise for a user name is right: What you're supposed to do is bind an instance of
threading.local
to a global variable, and then all of the "variables" it implements are secretly hash tables that get indexed by the current thread every time you access them.
None of the example code I've ever seen actually uses it this way and the official documentation is dogshit as usual. The main "feature" this seems to buy you is that the thread-local namespaces are tightly bound to a module and loosely bound to a thread (whereas having a dictionary in each thread object mapping modules to thread-local namespaces does the opposite). The only "advantage" of doing this is that each module can set defaults for its thread-local variables, which are then used for every freshly-spawned thread as opposed to having each module just set the thread-locals to their default values at import time and then having each child thread's thread-local namespaces start out as copies of the parent's, which would actually be useful. Otherwise, all you get is additional lookup overhead, a conceptual model that's different for the sake of being different, and a lot more "spooky action at a distance", and if I wanted I wanted spooky action at a distance, I would be using Ruby on Rails.