Basic concepts
Namespace
The mapping from name to object is mostly implemented by dictionary in python
- Built in
- global
- Local, local
Namespaces have a life cycle, which simply means that the function ends when it returns
Scope
python programs can directly access the body area of the namespace
- Built in
- global
- enclosing, which is outside the closure and not global
- Local, local
When reading variables, the search order must be from inside to outside. If there is no local variable, look for enclosing. If there is no global variable, look for built-in LEGB
There is no NameError. If there is one step, stop looking
x = 'global' def foo(): x = 'foo' def boo(): print(x) boo() foo() # foo
As for modifying variables (this refers to the direction of variables, not the modification of variable objects), only variables within their own scope can be modified, and neither the inner layer nor the outer layer can be modified
x='global' def foo(): x = 'foo' foo() print(x) # global
closure
The function is defined in the function. The inner function uses the variables of the outer function. At this time, the inner function is a closure
In fact, closures are much like objects, but if you need function granularity abstraction, use closures
global
The internal scope can directly modify global variables. No matter how many internal layers are, they can directly modify global variables
x='global' def a(): x='a' def b(): x='b' def c(): global x x = 'c' print('c,x',x) c() print('b,x',x) b() print('a,x',x) a() print('global,x',x) # output """c,x c b,x b a,x a global,x c"""
nonlocal
Modify the value of the enclosing scope variable
def foo(): x='foo' def doo(): nonlocal x x = 'doo' doo() print(x) foo() # doo
globals
The mapping that returns the global namespace is a dictionary. It seems that it can be changed. After testing, it is also effective
globals()['xxx'] = 10 print(xxx) # Output 10
locals
Return the mapping of local namespace. It's a dictionary. Don't modify it. It's useless to change it, official That's what I said
def foo(): locals()['xxx']=10 print(xxx) foo() # Error NameError: name 'xxx' is not defined
At the module level, use globals and locals to return the same dictionary
dir([object])
The first thing to return is a list
- If there are parameters, try to return a list of valid properties of the incoming object
- If there are no parameters, return the list of names in the current scope
That's how it works
print(sorted(dir()) == sorted(globals())) # True
dir attempts to return the most relevant but not the most comprehensive information of the object, which is mainly used for interaction, rather than to ensure the consistency of the results
Deep understanding
Why do namespaces have three scopes but four?
Namespace and scope can correspond to each other one by one. Why is there an enclosing area
Because of the nonlocal function, nonlocal can modify the local domain outside the local domain and the global domain. In fact, this region is the local domain of the outer function. In order to distinguish, it is called enclosing
The search order of variables is from the inside out. If there are multiple enclosing domains, it is also from the inside out, and nonlocal can only modify the first enclosing scope containing the variable, such as
x='global' def a(): x='a' def b(): x='b' def c(): nonlocal x x = 'c' print('c,x',x) c() print('b,x',x) b() print('a,x',x) a() print('global,x',x) # output """c,x c b,x c a,x a global,x global"""
nonlocal x in c can only modify X in b, and X in a is out of reach. If x is not defined in b, c can modify X of a, for example
x='global' def a(): x='a' def b(): def c(): nonlocal x x = 'c' print('c,x',x) c() print('b,x',x) b() print('a,x',x) a() print('global,x',x) # output """c,x c b,x c a,x c global,x global"""
When the closure is returned, the outer function namespace has been destroyed. Why can it be used later?
def foo(): x = 10 def boo(): print(x) return boo func = foo() func()
In the above typical closure example, when we execute func = foo(), the namespace of foo disappears and X disappears. But why can we still print(x) when we execute func()?
All closure functions have closure attributes, i.e__ closure__ Attribute. It is found that it is a tuple[cell]. The variables of the outer function used in the closure will have a corresponding cell object, and the cell of the cell object_ Contents attribute, you can directly obtain the value of the variable. See the following code directly
def foo(): x = 10 def boo(): print(x) return boo func = foo() print(func.__closure__) print(func.__closure__[0].cell_contents) # (<cell at 0x000001F4AA7CFE80: int object at 0x000001F4AA380210>,) # 10
Therefore, although the outer namespace disappears, the variables used by the closure maintain the relationship with the closure through the cell object
Each variable used by closures has a corresponding cell object, so when there are two closures using the same outer variable, they are__ closure__ The cell object in is the same. See the following example
def foo(): x = 10 def boo(): print(x) def coo(): print(x) print(id(boo.__closure__[0]) == id(coo.__closure__[0])) foo() # True
Who can generate scope?
It is often said that only function | class | module will generate a new scope, and code block if|for|while will not generate a new scope. In fact, various generator expressions will also generate a scope. You can try the following sentence
[print(locals()) for a in range(10)]
How to understand and modify the build in domain?
Running this code, we will have the illusion that the global scope code has modified the build in scope variable!
class Foo: def __init__(self, *args, **kwargs): print('Foo') set = Foo def foo(): print(set([1, 2, 2, 3])) foo() print(set([2, 2, 2, 2])) # Foo # <__main__.Foo object at xxx> # Foo # <__main__.Foo object at xxx>
This is not the case. We just added a variable named set in the global namespace. Just because the variable search order is LEGB, we stop when we find one. Therefore, when other scopes use set, we will get it when we search the global. If we don't add this set variable, we usually need to search build in. See the following code, Only one more set is output twice
print(locals()) set = 1 print(locals())
If you define the same variables as the built-in domain, you can import the builtins module to use the variables of the real built-in domain
import builtins class Foo: def __init__(self, *args, **kwargs): print('Foo') set = Foo print(builtins.set([1,2,2,2,3])) # {1, 2, 3}
How to really modify the variables in the build in domain? I think you should already know
import builtins class Foo: def __init__(self, *args, **kwargs): print('Foo') builtins.set = Foo print(set([1,1,1,1,1,2])) print(builtins.set([1,2,2,2,3])) # Foo # <__main__.Foo object at 0x0000022E111C8FD0> # Foo # <__main__.Foo object at 0x0000022E111C8FD0>
Postscript
This article comes up with a series of problems when the author searches for closures, and is written according to official documents and some articles on the Internet. It is not guaranteed that they are all right, but they all run through code testing and thinking. In fact, these functions and problems will not be encountered in daily coding, but if they can be sorted out clearly, it will certainly be helpful to understand Python; I can only think so much about the scope for the time being. If you have other knowledge, please add it! end.
If you have any questions, welcome to communicate!