zoukankan      html  css  js  c++  java
  • Python名称空间与作用域

    Python名称空间与作用域

    Name

    Python 的Name(名称)对象的一个Identifier(标识)。在 Python 里面一切皆对象,名称就是用来引用对象的。

    a = 2
    print(id(a))  # 1966107712
    print(id(2))  # 1966107712
    
    # 可以看到,两都均指向同一个对象
    

    a = 2这个语句中,2是个存储在内存中的一个对象,名称a则会引用2这个对象,“引用”的含义是指可以通过名称a来使用2这个对象。我们可以使用id()函数来获取对象的地址.

    更为复杂的代码:

    a = 2
    print("id(a) = %s" % id(a))  # id(a) = 1966107712
    a = a + 1
    print("id(2) = %s" % id(2))  # id(2) = 1966107712
    print("id(a) = %s" % id(a))  # id(a) = 1966107744
    print("id(3) = %s" % id(3))  # id(3) = 1966107744
    b = 2
    print("id(b) = %s" % id(2))  # id(b) = 1966107712
    print("id(2) = %s" % id(b))  # id(2) = 1966107712
    

    1. 创建对象2, 名称a引用了对象2
    2. 执行操作a = a + 1,这时候对象3被创建,名称a引用了对象3。所以此时id(a)id(3)指向相同内存地址相同;此时对象2任然存在内存中;
    3. 执行操作b = 2,不会重复创建对象2(内存中已经存在对象2), 名称b引用了对象2,所以id(2)id(b)相同

    Python 在执行变量的赋值时,并不会重复创建一个对象的事实。名称采用动态绑定的机制使得 Python 更加高效,同一个名称可以引用不同类型的对象

    a = 5
    a = "hello world"
    a = [1, 2, 3]
    
    # a先后引用了数字,字符串,列表的类型的对象,这在 Python 中完全是合法的
    # 此时a指向了对象[1, 2, 3]
    

    名称空间

    NameSpace定义

    名称到对象的映射,名称空间是一个字典的实现,键为变量名,值是变量对应的值。各个名称空间是独立没有关系的,一个名称空间中不能有重名,但是不同的名称空间可以重名而没有任何影响

    Python解释器大致执行过程:

    ​ Python解释启动时,开辟一块内存空间(内置名称空间);逐行读取解释一个模块的代码,在内存中再开辟一块内存空间(全局名称空间),每当遇到遇到一个变量的时候,就把变量名称和值(对象)之间的对应关系记录在内存中(全局名称空间),但是当遇到函数名时,解释器只会将函数名象征性地读入内存(全局名称空间)。

    ​ 等到函数被执行时,Python解释器会再开辟一块内存(局部名称空间)来存储这个函数内部的内容(函数中的各种对象和名称的对应关系),当函数执行完毕后,局部名称空间会被回收清空。

    分类

    • Built-in:内置名称空间,例如:Python 的内置函数,如,abs()
    • Global:全局名称空间(模块名称空间,在模块内定义的名称)
    • Local:局部名称空间,例如:在函数(function)或者类(class)被调用时,其内部包含的名称

    生命周期

    • Built-in(内置名称空间)在python解释器启动时创建,一直保留直到解释器退出
    • Global(全局名称空间)在模块被加载时创建,通常一直保留直到python解释器退出
    • Local(局部名称空间)在函数被调用时才被创建,但函数返回结果或抛出异常时被删除。(每一个递归函数都拥有自己的名称空间)

    名空间创建顺序:

    python解释器启动 ->创建内建名称空间 -> 加载模块 -> 创建全局名称空间 ->函数被调用 ->创建局部名称空间

    名称空间销毁顺序:

    名称函数调用结束 -> 销毁函数对应的局部名称空间 -> python虚拟机(解释器)退出 ->销毁全局名称空间 ->销毁内建名称空间


    变量的作用域

    Scope(作用域)

    • Python的一块文本区域,这个区域中,名称空间可以被“直接访问”。这里的直接访问指的是试图在名称空间中找到名字的绝对引用(非限定引用)
      • 直接引用:直接使用名字访问的方式,如name,这种方式尝试在名字空间中搜索名字name
      • 间接引用:使用形如objname.attrname的方式,即属性引用,这种方式不会在命名空间中搜索名字attrname,而是搜索名字objname,再访问其属性
    • 作用域是针对变量而言,指申明的变量在程序里的可应用范围。或者称为变量的可见性
    • 只有当变量在Module(模块)、Class(类)、def(函数)中定义的时候,才会有作用域的概念,代码块不会产生作用域

    高级语言对数据类型的使用过程

    一般的高级语言在使用变量时,都会有下面4个过程。

    1. 声明变量:让编译器或解释器知道有这一个变量的存在
    2. 定义变量:为不同数据类型的变量分配内存空间
    3. 初始化:赋值,填充分配好的内存空间
    4. 引用:通过引用对象(变量名)来调用内存对象(内存数据)

    作用域分类

    • L(local)局部作用域

      # 局部变量:包含在def关键字定义的语句块中,即在函数中定义的变量。每当函数被调用时都会创建一个新的局部作用域。Python中也有递归,即自己调用自己,每次调用都会创建一个新的局部命名空间。在函数内部的变量声明,除非特别的声明为全局变量,否则均默认为局部变量。有些情况需要在函数内部定义全局变量,这时可以使用global关键字来声明变量的作用域为全局。局部变量域就像一个 栈,仅仅是暂时的存在,依赖创建该局部作用域的函数是否处于活动的状态。所以,一般建议尽量少定义全局变量,因为全局变量在模块文件运行的过程中会一直存在,占用内存空间。
      # 注意:如果需要在函数内部对全局变量赋值,需要在函数内部通过global语句声明该变量为全局变量。
      
    • E(enclosing)嵌套作用域

      # E也包含在def关键字中,E和L是相对的,E相对于更上层的函数而言也是L。与L的区别在于,对一个函数而言,L是定义在此函数内部的局部作用域,而E是定义在此函数的上一层父级函数的局部作用域。主要是为了实现Python的闭包,而增加的实现。
      
    • G(global)全局作用域

      # 即在模块层次中定义的变量,每一个模块都是一个全局作用域。也就是说,在模块文件顶层声明的变量具有全局作用域,从外部开来,模块的全局变量就是一个模块对象的属性。
      
      # 注意:全局作用域的作用范围仅限于单个模块文件内
      
    • B(built-in)内置作用域

      # 系统内固定模块里定义的变量,如预定义在builtin 模块内的变量。
      

    LEGB法则

    搜索变量名的优先级:L(局部作用域 )>E( 嵌套作用域) >G( 全局作用域) >B( 内置作用域)

    LEGB法则
    当在函数中使用未确定的变量名时,Python会按照优先级依次搜索4个作用域,以此来确定该变量名的意义。首先搜索局部作用域(L),之后是上一层嵌套结构中def或lambda函数的嵌套作用域(E),之后是全局作用域(G),最后是内置作用域(B)。按这个查找原则,在第一处找到的地方停止。如果没有找到,则会出发NameError错误。

    与名称空间的关系

    现在,名称空间持有了名字。作用域是Python的一块文本区域,即一块代码区域,需要代码区域引用名字(访问变量),那么必然作用域与名称空间之间就有了联系。

    顾名思义,名字作用域就是名字可以影响到的代码文本区域,名称空间的作用域就是这个命名空间可以影响到的代码文本区域。那么也存在这样一个代码文本区域,多个名称空间可以影响到它。
    作用域只是文本区域,其定义是静态的;而名字空间却是动态的,只有随着解释器的执行,名称空间才会产生。那么,在静态的作用域中访问动态名称空间中的名字,造成了作用域使用的动态性。

    那么,可以这样认为:

    静态的作用域,是一个或多个名称空间按照一定规则叠加影响代码区域

    运行时动态的作用域,是按照特定层次组合起来的命名空间。

    在一定程度上,可以认为动态的作用域就是名称空间。在后面的表述中,我会把动态的作用域与其对应命名空间等同起来。

    作用域应用

    free variable

    free variable的定义:

    If a variable is used in a code block but not defined there, it is a free variable.
    

    我们已经了解了作用域有LEGB的层次,并按顺序搜索名字。按照搜索顺序,当低层作用域不存在待搜索名字时,引用高层作用域存在的名字,也就free variable(自由变量)

    example_1:

    def low_scope():
        print(s)  # L层不存在s,引用G层名字s
        
    
    s = 'upper scope'
    low_scope()
    

    example_2:

    def low_scope():
        s = "lower scope"  # 在局部作用域定义s
    
    
    s = "upper scope"  # 全局作用域定义s
    low_scope()
    print(s)  # upper scope
    
    """
    A special quirk of Python is that – if no global statement is in effect – assignments to names always go into the innermost scope.
    """
    

    即赋值语句影响局部作用域,赋值语句带来的影响是绑定或重绑定,但是在当前局部作用域的命名空间中,并没有s这个名字,因此赋值语句在局部作用于定义了同名名字s,这与外层作用域中的s并不冲突,因为它们分属不同名称空间。
    这样,全局作用域的s没有被重绑定,结果就很好解释了

    example_3:

    def low_scope():
        lst[0] = 2
    
    
    lst = [1, 2]
    low_scope()
    print(lst)  # [2, 2]
    

    当涉及可变对象时,情况又有所不同了, lst[0] = 2并不是对名字l的重绑定,而是对l的第一个元素的重绑定,所以没有新的名字被定义。因此在函数中成功更新了全局作用于中l所引用对象的值

    example_4进行对比

    example_4:

    def low_scope():
        lst = [2, 2]
        
    
    lst = [1, 2]
    low_scope()
    print(lst)  # [1, 2]
    

    综上,可以认为:

    自由变量可读不可写

    global和nonlocal

    总是存在打破规则的需求:

    在低层作用域中需要重绑定高层作用域名字,即通过自由名字重绑定于是global语句和nonlocal语句因运而生。

    global语句是适用于当前代码块的声明语句。列出的标识符被解释为全局名字。虽然自由名字可以不被声明为global就能引用全局名字,但是不使用global关键字绑定全局名字是不可能的。

    nonlocal语句使得列出的名字指向最近封闭函数中绑定的名字,而不是全局名字。默认的绑定行为会首先搜索局部作用域。nonlocal语句使得在内层函数中重绑定外层函数作用域中的名字成为可能,即使同名的名字存在于全局作用域。

    官方案例:

    def scope_test():
    
        def do_local():
            spam = "local spam"
    
        def do_nonlocal():
            nonlocal spam  # 当外层作用域不存在spam名字时,nonlocal不能像global那样自作主张定义一个
            spam = "nonlocal spam"  # free variable spam经nonlocal声明后,可以做重绑定操作了,可写的。
    
        def do_global():
            global spam  # 即使全局作用域中没有名字spam的定义,这个语句也能在全局作用域定义名字spam
            spam = "global spam"  # 自有变量spam经global声明后,可以做重绑定操作了,可写的。
    
        spam = "test spam"
        do_local()
        print("After local assignment:", spam)
        do_nonlocal()
        print("After nonlocal assignment:", spam)
        do_global()
        print("After global assignment:", spam)
    
    
    scope_test()
    print("In global scope:", spam)
    
    
    # After local assignment: test spam
    # After nonlocal assignment: nonlocal spam
    # After global assignment: nonlocal spam
    # In global scope: global spam
    

    一些坑

    example_1:

    def func():
        variable = 300
        print(variable)
    
    
    variable = 100
    func()
    print(variable)
    

    example_2:

    variable = 300
    
    
    def test_scope():
        print(variable)
        variable = 200
    
    
    test_scope()
    print(variable)
    
    # UnboundLocalError: local variable 'variable' referenced before assignment
    

    example_2会报出错误,因为在执行程序时的预编译能够在test_scope()中找到局部变量variable(对variable进行了赋值)。在局部作用域找到了变量名,所以不会升级到嵌套作用域去寻找。但是在使用print语句将变量variable打印时,局部变量variable并有没绑定到一个内存对象(没有定义和初始化,即没有赋值)。本质上还是Python调用变量时遵循的LEGB法则和Python解析器的编译原理,决定了这个错误的发生。所以,在调用一个变量之前,需要为该变量赋值(绑定一个内存对象)。

    Python解释器编译原理:

    Python中的模块代码在执行之前,并不会经过预编译,但是模块内的函数体代码在运行前会经过预编译,因此不管变量名的绑定发生在作用域的那个位置,都能被编译器知道。Python虽然是一个静态作用域语言,但变量名查找是动态发生的,直到在程序运行时,才会发现作用域方面的问题

  • 相关阅读:
    XML错误信息Referenced file contains errors (http://www.springframework.org/schema/beans/spring-beans-4.0.xsd). For more information, right click on the message in the Problems View ...
    Description Resource Path Location Type Cannot change version of project facet Dynamic Web Module to 2.3.
    maven创建web报错Cannot read lifecycle mapping metadata for artifact org.apache.maven.plugins:maven-compiler-plugin:maven-compiler-plugin:3.5.1:runtime Cause: error in opening zip file
    AJAX跨域
    JavaWeb学习总结(转载)
    JDBC学习笔记
    Java动态代理之JDK实现和CGlib实现
    (转)看懂UML类图
    spring boot配置使用fastjson
    python3下django连接mysql数据库
  • 原文地址:https://www.cnblogs.com/pankypan/p/11086792.html
Copyright © 2011-2022 走看看