zoukankan      html  css  js  c++  java
  • Python名称空间与作用域及闭包函数

    名称空间

    存放名字与值的内存地址对应关系的空间。

    名称空间分为三类

    内置名称空间:

    存放Python解释器内置的名字,如input,print,list等。

    存活周期:Python解释器启动则产生,Python解释器关闭则回收,只存在一个。

    全局命名空间:

    运行顶级代码所定义的名字,或者说不是函数内定义、也不是内置的,剩下的都是全局名称空间。

    存活周期:Python文件执行产生,在定义全局名称时会在全局名称空间内将名称放进去,文件执行结束立即回收,只存在一个。

    # 直接在一行开头定义一个变量,或者定义一个函数,那么这个变量名与函数名就属于全局名称空间。
    x = 10
    
    def func():
        pass
    
    # 流程控制语句缩进内的定义的名称,也是属于全局名称空间。
    if 10 > 3:
        a = 111
    

    局部名称空间:

    在函数体的运行中开辟的局部的空间,也叫做临时命名空间,同一个函数调用多次会产生多个局部名称空间。

    存活周期:函数体执行时产生,函数运行结束,这个空间也会回收。

    # 函数内部定义的名称。
    def func():  # func本身是全局名称空间。
        x = 10  # x是局部名称空间。
    
    func()
    

    名称空间实际上是相互隔离独立的一个个空间,并没有包含关,嵌套关系仅为了帮助理解。

    加载到内存的顺序:

    • 1.内置名称空间

    • 2.全局名称空间

    • 3.临时名称空间

    取值顺序:

    按照LEGB原则就近取值,取值顺序单向不可逆。

    LEGB原则:

    • Local本地 --> Enclosed嵌套函数的外层函数内部 --> Global全局 --> Builtin内置

    (从局部开始找时)局部名称空间 --> 全局名称空间 --> 内置名称空间

    input = 333
    print(input) # 此时是从全局开始取值,input()函数则不会被查找到
    # 333
    

    名称空间的嵌套关系是在函数定义阶段,即检测语法时确定的,与函数调用的位置无关,与函数定义位置有关。

    函数的取值位置是个很绕的知识点,最好一步一步分析函数的执行步骤,与惯性思维做斗争。举几个例子帮助学习。

    示例一:

    x = 111  # 1、首先在全局定义x = 111
    def func():  # 2、定义func,此时已确定x取值先从func局部取值,局部未找到则向全局取值。
        print(x)  # 5、x向全局取值,此时x = 222,所以最终结果为222。
    
    x = 222  # 3、此时全局定义x = 222
    func()  # 4、调用func
    222
    
    
    x = 111  # 1、首先在全局定义x = 111
    def func():  # 2、定义func,此时已确定x取值先从func局部取值,局部未找到则向全局取值。
        print(x)  # 4、x向全局取值,此时x = 111,所以最终结果为111。
    
    func()  # 3、调用func。
    x = 222  # 5、此时在全局定义x = 222,但func函数已经调用,函数内的x已经取值为111。
    111
    

    示例二:

    x = 1  # 1、在全局定义x = 1
    def func():  # 2、定义func函数,此时已确定x取值先从func局部取值,局部未找到则向全局取值。
        print(x)  # 7、打印x,x此时从全局取值,结果为1
    
    def foo():  # 3、定义foo函数
        x = 222  # 5、在foo的局部名称空间内定义x = 222,并不会干扰到全局的x
        func()  # 6、调用func
    
    foo()  # 4、调用foo
    1
    

    示例三:

    input = 111  # 1、在全局定义input = 111。
    def f1():  # 2、定义f1函数。
        def f2():  # 4、在f1局部名称空间内定义f2。
            input = 333  # 7、在f2局部名称空间内定义input = 333。
            print(input) # 8、input先在当前所在名称空间内取值,此时input值为333。
        input = 222  # 5、在f1局部名称空间内定义。
        f2()  # 6、调用f2,执行函数体代码。
    
    f1()  # 3、调用f1,执行f1函数体代码。
    333
    

    示例四:

    count = 1
    def func():
    	count = 100
    	print(count)  # 直接在当前名称空间取值,count为100。
    func()
    100
    

    示例五:

    def f1():
        m=111
        return m
    
    def f2():
        print(res)  # 此时已确定res取值先从f2局部取值,局部未找到则向全局取值。
    
    def f3():
        print(res)  # 此时已确定res取值先从f3局部取值,局部未找到则向全局取值。
    
    res=f1()  # f1() = m = 111
    f2()
    f3()
    
    111
    111
    

    示例六:

    x=111
    
    def f1():
        print(x)  # 定义阶段已经确定先从局部取值,但是局部x是先取值再定义,所以保存
        x = 222
    
    f1()
    UnboundLocalError: local variable 'x' referenced before assignment
    

    示例七:

    m = [111,]
    def func(y):
        m.append(333)
        print(y)
    
    func(m)
    m.append(222)
    print(m)
    
    [111, 333]
    [111, 333, 222]
    

    名称空间不仅是Python独有,这是整个计算机领域的一种设计,在Python之禅的最后一句话。

    import this
    ...
    Namespaces are one honking great idea -- let's do more of those!
    

    作用域:

    变量的生效范围,分为全局作用域和局部作用域。

    全局作用域:

    • 内置名称空间,全局名称空间。全局存活,全局有效。
    # x就属于全局名称空间,在全局名称空间和局部名称空间都能访问到。
    x = 10
    def f1():
        print(x)
    print(x)
    f1()
    10
    10
    

    globals()

    返回包含当前范围的全局变量的字典。

    x = 111
    print(globals())
    
    {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000000002061940>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'D:/desktop/code.py', '__cached__': None, 'x': 111}
    

    局部作用域:

    • 局部名称空间,临时存活,局部有效。
    • 局部作用域可以引用全局作用域的变量,但不可改变。
    x = 10
    def f1():
        x = 20
    
    f1()
    print(x)
    

    locals()

    返回包含当前作用域的局部变量的字典。

    x = 111
    def func():
        a = 10
        b = 20
        print(locals())
    func()
    {'a': 10, 'b': 20}
    

    global

    只能在局部名称空间使用。在局部声明一个全局作用域的变量。在 global 语句中列出的名称不得在同一代码块内该 global 语句之前的位置中使用。

    global 语句中列出的名称不得被定义为正式形参,不也得出现于 for 循环的控制目标、class 定义、函数定义、import 语句或变量标注之中。

    局部名称空间不能修改全局名称空间的不可变数据类型的值,只能引用。

    c = 1
    def func():
        c += 1		# 不可更改,在更改时会从先局部名称空间取c的值,然后再赋值给c,造成先引用、后定义的保错
        print(c)
    func()
    #  local variable 'count' referenced before assignment
    

    对于可变类型,也不能使用先取值后修改再赋值给原变量名这种方式,而是使用内置方法来修改。

    c = [1,2,3]
    def func():
        c += [3,4,5]  # 相当于 c = c + [3,4,5]
        print(c)
    func()
    UnboundLocalError: local variable 'c' referenced before assignment
    
    c = [1,2,3]
    def func():
        c.extend([3,4,5])
        print(c)
    func()
    [1, 2, 3, 3, 4, 5]
    

    使用global关键字,可以在局部修改一个全局变量。

    c = 1
    def func():
        global c
        c += 1
        print(c)
    func()
    2
    

    nonlocal

    只能在函数嵌套定义的内层函数中使用,在第一层局部名称空间内使用会报错。用作函数嵌套定义中,在内层函数中声明一个外层局部名称空间的变量。

    nonlocal 语句中列出的名称不得与之前存在于局部作用域中的绑定相冲突。

    x = 111
    
    def f1():
        x = 222
    
        def f2():
            nonlocal x
            x = 333
    
        f2()
        print(x)
    
    f1()
    333
    

    闭包函数定义:

    • 闭函数:是嵌套在函数中的函数。
    • 包函数:内层闭函数引用外层函数一个非全局的变量,就是闭包。
    def f1():
        x = 10
        def f2():
            print(x)
    
    

    闭包的作用:

    被引用的非全局变量也称作自由变量,这个自由变量会与内层函数产生一个绑定关系。
    自由变量不会在内存中消失。

    1、保证数据安全。函数外无法访问自由变量。

    def func1():
        li = []
        def func2(x):
            li.append(x)
            return li
        ruturn func2
    f = func1()
    print(f(100))   # [100]
    print(f(100))   # [100, 100]
    print(f(100))   # [100, 100, 100]
    #li就是自由变量,并没有消失,但无法从函数外访问。
    

    2、为内部函数传参。

    返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域。

    def f1(x):
        def f2():
            print(x,y)  # 优先使用外层的作用域,不会被全局所影响。
        x = 10
        y = 20
        return f2
    
    x = 1
    y = 2
    f1(x)()
    10 20
    

    如何判断一个嵌套函数是不是闭包:

    print(func.__code__.co_freevars)
    # 只要返回值有自由变量那么就是闭包
    
    def func():
        x = 10
        def f1():
            print(x)
        return f1
    
    print(func().__code__.co_freevars)
    ('x',)
    
  • 相关阅读:
    BZOJ 4236~4247 题解
    OCUI界面设计:滚动视图与分页控件初探
    大数乘法
    Effective C++ 11-17
    [Matlab+C/C++] 读写二进制文件
    Java 反射 方法调用
    如何使用LaTeX让自己不乱?
    LaTeX 相对于 Word 有什么优势?
    你在发表理科学术文章过程中有哪些经验值得借鉴?
    破译手势在对话中的意义
  • 原文地址:https://www.cnblogs.com/ChiRou/p/13427908.html
Copyright © 2011-2022 走看看