Python Nonlocal Statement

Python nonlocal statement

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

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.

Can I get the value of a non-local variable without using the nonlocal statement?

I don't get your context that you have to use same name.

Anyway, you can capture outer function's locals as nonlocal variable.

x = "global"


def f(x="nonlocal"):
nonlocals = locals()

def g():
x = "local"
print(x)
print(nonlocals['x'])
print(globals()["x"])

return g


f()()

output:

local
nonlocal
global

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.

Python nonlocal statement in a class definition

Lexical scoping applies only to function namespaces, otherwise methods defined inside a class would be able to "see" the class level attributes (which is by design - those attributes must instead be accessed as attributes of self inside the method).

The same limitations that cause the class level variables to be skipped over by references from methods also keep the nonlocal keyword from working its magic. (global does work though, since that doesn't rely on the lexical scoping machinery)

using nonlocal or global inside a class body

nonlocal wouldn't work in any event, because variables have only one scope in the case where nonlocal applies (function locals, which are subtly different from class definition scope); by trying to use nonlocal, you'd say model was never part of the class definition scope, just something from outside it.

I personally prefer your kinda hacky reassignment so _model outside the class and model inside the class don't conflict, but if you hate it, there is an option to directly access the class-in-progress's namespace, vars() (or locals(); the two are equivalent in this case, but I don't think of the class scope as being locals, even though they act a lot like it).

Because the scope is not really a function scope, you can in fact mutate it through the dict from vars/locals, allowing your desired result to look like:

def generate_meta_options(model, design):
class Meta:
vars().update(
model=model,
design=translate_old_design_spec_to_new_format(design)
)
return Meta

Using the keyword argument passing form of dict.update means the code even looks mostly like normal assignment. And Python won't complain if you then use those names earlier (seeing outer scope) or later (seeing newly defined names) in the class definition:

def generate_meta_options(model, design):
print("Before class, in function:", model, design) # Sees arguments
class Meta:
print("Inside class, before redefinition:", model, design) # Sees arguments
vars().update(
model=model,
design=design+1
)
print("Inside class, after redefinition:", model, design) # Sees class attrs
print("After class, in function:", model, design) # Sees arguments
return Meta

MyMeta = generate_meta_options('a', 1)
print(MyMeta.model, MyMeta.design)

Try it online!

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)
...


Related Topics



Leave a reply



Submit