All namespaces | scopes | closures | global | nonlocal | global | locales

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!

Keywords: Python

Added by ferrit91 on Thu, 17 Feb 2022 17:42:28 +0200