- Closure functions can refer to variables from any of the scopes in wihch they were defined.
- By default, closures can't affect enclosing scopes by assigning variables.
- In Python 3, use the nonlocal statement to indicate when a closure can modify a variable in its enclosing scopes.
- In Python 2, use a mutable value (like a single-item list) to work around the lack of the nonlocal statement
- Avoid using nonlocal statements for anything beyound simple functions.
Effective Python
Scope
Say you want to sort a list of numbers but prioritize one group of numbers to come first.
def sort_priority(values, group):
def helper(x):
if x in group:
return (0, x)
return (1, x)
values.sort(key=helper)
In [2]: numbers = [8, 3, 1, 2, 4, 5, 7,6]
In [3]: group = {2, 3, 5, 7}
In [4]: sort_priority(numbers, group)
In [5]: print(numbers)
[2, 3, 5, 7, 1, 4, 6, 8]
- Python supports closures: functions that refer to variables from the scope in which they were defined. This why the hlper function is able to access the group argument to sort_priority.
- Functions are first-class objects in Python, meaning you can refer to them directly, assign them to variables, pass them as arguments to other functions, compare them in expressions and if statements, etc. This is how the sort method cna accept a closure function as the key argument.
- Python has specific rules for comparing tuples. If first compares items in index zero, then index one, then index two, and so on. This is why the return value from the helper closure causes the sort order to have two distinct groups.
Cause Scope Bug
def sort_priority2(numbers, group):
found = False # Scope: sort_priority2
def helper(x):
if x in group:
found = True # Scope: helper -- Bad
return 0, x
return 1, x
numbers.sort(key=helper)
return found
>>> l = [4, 6,2,5,7,9, 0]
>>> found = sort_priority2(l, [2,5,8])
>>> found
False
>>> l
[2, 5, 0, 4, 6, 7, 9]
- The current function's scope
- Any enclosing scope (like other containing functions)
- The scope of the module that contains the code(also called the global scope)
- The built-in scope(that contains functions like len and str)
Getting Data out
nonlocal
Python 3
def sort_priority2(numbers, group):
found = False # Scope: sort_priority2
def helper(x):
nonlocal found
if x in group:
found = True # Scope: helper -- Bad
return 0, x
return 1, x
numbers.sort(key=helper)
return found
The nonlocal statement is used to indicate that scope traversal should happen upon assiginment for a specific variable name. The only limit is that nonlocal won't traverse up to the module-level scope (to avoid polluting globals)