Nonlocal Keyword in Python 2.X

When to use nonlocal keyword?

What you are facing there is that in one case, you have in the vareiable a mutable object, and you operate on the object (when it is a list) - and in the other case you are operating on an imutable object and using an assignment operator (augmented assingment +=) .

By default, Python locates all non-local variables that are read and use then accordingly - but if a variable is assigned to in the inner scope, Python assumes it is a local variable (i.e. local to the inner function), unless it is explicitly declared as nonlocal.

syntax error on nonlocal statement in Python

nonlocal only works in Python 3; it is a new addition to the language.

In Python 2 it'll raise a syntax error; python sees nonlocal as part of an expression instead of a statement.

This specific example works just fine when you actually use the correct Python version:

$ python3.3
Python 3.3.0 (default, Sep 29 2012, 08:16:08)
[GCC 4.2.1 Compatible Apple Clang 3.1 (tags/Apple/clang-318.0.58)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> def outer():
... x = 1
... def inner():
... nonlocal x
... x = 2
... print("inner:", x)
... inner()
... print("outer:", x)
...

Python nonlocal statement / keyword

Compare this, without using nonlocal:

x = 0
def outer():
x = 1
def inner():
x = 2
print("inner:", x)

inner()
print("outer:", x)

outer()
print("global:", x)

# inner: 2
# outer: 1
# global: 0

To this, using nonlocal, where inner()'s x is now also outer()'s x:

x = 0
def outer():
x = 1
def inner():
nonlocal x
x = 2
print("inner:", x)

inner()
print("outer:", x)

outer()
print("global:", x)

# inner: 2
# outer: 2
# global: 0

If we were to use global, it would bind x to the properly "global" value:

x = 0
def outer():
x = 1
def inner():
global x
x = 2
print("inner:", x)

inner()
print("outer:", x)

outer()
print("global:", x)

# inner: 2
# outer: 1
# global: 2

Is there something like 'nonlocal' in Python 3?

Nope, the best alternative for it is function attributes.

Python's nonlocal keyword - is this good practice?

I think that under the circumstances you describe, the nonlocal approach objectively makes more sene than the other one you show. Your goal is to have a counter outside your function and a way to keep track of the result when it is found regardless of where you are in the recursion.

nonlocal completely encapsulates that information in a namespace dedicated to the particular run of your outer function. There is really no downside here.

Using instance attributes makes that information available to anyone using the instance. This is unnecessary conceptually, marginally slower, and not thread-safe. While thread safety is often not a concern in Python, it does make your code much less robust. Between that and encapsulation, I would definitely stay away from this approach.

I would suggest a third possibility here: using return values. In that case, your really don't need any external namespace. I don't necessarily recommend this over using nonlocal, but it's worth mentioning. Here is a sample implementation, which as you can see is much more verbose than your solution:

class Solution:
def kthSmallest(self, root: TreeNode, k: int) -> int:
def traverse_inorder(root, target):
if not root:
return 0, None
left_count, item = traverse_inorder(root.left, target)
if left_count == target - 1:
return left_count + 1, root.val
elif left_count < target:
right_count, item = traverse_inorder(root.right, target - left_count - 1)
return left_count + right_count + 1, item
else: # left_count == target
return left_count, item
count, ans = traverse_inorder(root, k)
if count < k:
raise ValueError('Insufficient elements')
return ans

There are many ways to do this with return values. Here, I compute the number of elements in each subtree, up to the target value. A non-none item is only returned if the exact number of elements is found.

How does python nonlocal keyword implement? Can it save space?

Python's data model defines User-defined functions as having a __closure__ attribute, which reify the function closure: the readable/writeable values of enclosing scopes. __closure__ is None or a tuple of cells.

The standard currently (3.10) only defines that a cell has a cell_contents attribute to represent the value of the cell. The CellType makes no guarantees what it provides.

Notably, whether a cell is writeable is not determined by whether a function captures the closure as readable (bare usage) or readable/writeable (nonlocal declaration). Both are the same kind of cell.


In practice, CPython¹ represents __closure__ as a regular tuple and each cell as a 40 byte object that holds a pointer to its value.

>>> def outer(a = 3):
... def inner():
... print(a) # `a` is captured from outer scope
... return inner
>>> outer()
<function __main__.outer.<locals>.inner()>
>>> outer().__closure__
(<cell at 0x10eac2ca0: int object at 0x109f26d30>,)
>>> outer().__closure__[0].cell_contents
3
>>> # size of the closure tuple and cell in bytes
>>> sys.getsizeof(outer().__closure__), sys.getsizeof(outer().__closure__[0])
(48, 40)

The __closure__ itself belongs to the function object, whereas a cell is shared between all functions closing over the same variable.

In contrast, a local variable is stored as an array of pointers – each 8 byte. The local storage belongs to the function call, so calling a function multiple times also creates multiple such pointers.

For reference, just the shell of the above inner function object is 136 bytes. Its name and fully-qualified name are 54 and 69 bytes, respectively. Its bytecode is 45 bytes. There are many additional costs for things that you likely do not even know exist.

Keep that in mind when trying to safe individual chunks of 8 bytes.


¹CPython 3.8.12 [Clang 11.0.0 (clang-1100.0.33.17)], 64 bit build.



Related Topics



Leave a reply



Submit