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
Using Moviepy, Scipy and Numpy in Amazon Lambda
Python Accessing Nested JSON Data
How to Display a Pandas Data Frame with Pyqt5/Pyside2
How to Mark a Portion of a Text Widget as Readonly
Setting an Environment Variable in Virtualenv
How to Trigger Function on Value Change
How to Create a Read-Only Class Property in Python
Encode and Assemble Multiple Features in Pyspark
How to Overwrite/Print Over the Current Line in Windows Command Line
Assign Operator to Variable in Python
How to Insert Pandas Dataframe via MySQLdb into Database
Check If a Process Is Running or Not on Windows
Should I Call Close() After Urllib.Urlopen()
Accessing Every 1St Element of Pandas Dataframe Column Containing Lists
How to Get the Current Time in Milliseconds in Python
Calculating Arithmetic Mean (One Type of Average) in Python
Display Fullscreen Mode on Tkinter
How to Capture Output of Python's Interpreter and Show in a Text Widget