python作用域初探

python作用域初探

前言:在正式探讨前先抛出几个基本的概念,这几个概念是官方文档中有声明的概念,不过这些概念都包含有自己的一些理解,所以难免有些不太准确,请大家多多指正。

1.基本概念:

    命名空间:名称到对象值的一个映射空间。

    作用域:一个代码块区域,此代码块区域可以访问某些命名空间(访问就是基于作用域)。

    直接访问:通过不加前缀就可以访问某个变量。(这就是我们讨论作用域的目的)

    属性访问:通过前缀来访问某个变量,如:copy.deepcopy()表示从copy的命名空间中找到变量deepcopy并使用。

        请记住:作用域可以访问多个命名空间(层层访问),某个命名空间是依赖某个作用域生成(不完全正确)。

2.变量搜索路径

    我们运行代码时就是要找到当前作用域可以直接访问的命名空间,找到这个对应关系就可以确定变量的搜索路径,那么变量对应的值就可以找到并使用,并且各个命名空间的变量不会互相影响。

那么对于一个代码块,它可以直接访问(不是属性访问)到的命名空间有哪些呢?实际上这个访问顺序遵循一个叫LEGB-rule的规则:

    (1)先在局部作用域对应的命名空间寻找,比如一个函数内部的代码块就会首先在自己函数内部的命名空间寻找
    (2)如果局部作用域没找到,它就会进入上一层作用域 (还不是全局作用域) 对应的命名空间寻找,比如闭包结构就是这种情况
    (3)接着会在全局作用域寻找,即当前模块的命名空间
    (4)最后会 __builtin__内置集合中寻找。

    说到这里其实作用域就已经说完了,如果在一个代码块中,它调用一个变量,然后挨个层级搜索不就完了,找不到就报错,找到就使用,这有什么难的?如果真这么简单,那我怎么还会写这篇文章。

    首先来看第一个栗子:

栗子1:
a = "global var"
def f():
    b = "local val"
    def f2():
        b = "abc"
        print(a)
        print(b)
    f2()
    print(b)
f()
#输出结果:
#global
#abc
#local val

    解释一下以上执行过程,在遇到print(a)时,此时是在f2这个函数的局部作用域中,没有搜寻到a,然后往上层的作用域也就是f函数的作用域寻找,也没有找到,接着到达全局作用域寻找,找到了a = "global var"。在遇到print(b)时此时也在f2这个函数的作用域中,找到了b = "abc"。接着在f2()运行完后,运行第二个print(b)它首先在f函数的作用域中寻找(闭包的空间),找到了b = "local val"。是不是很简单?其实还没完,如果我在这样修改呢?

栗子2:
a = "global var"
def f():
    def f2():
        print(a)
        a = "local var"
    f2()
f()
#输出:UnboundLocalError: local variable 'a' referenced before assignment

    这是为什么呢?这是因为虽然python是一门动态语言但是为了提高效率,python会把一些工作在编译时就完成,而不会等到动态执行时才完成。比如在创建局部变量时,还没有执行某代码块的语句,局部命名空间就创建好了。在这个栗子中,当代码运行刚进入f2时在f2内就创建了一个命名空间将变量a添加进去,还没实际赋值(这里我不了解底层的实现原理),但是执行print(a)时却找不到a的实际值,因为还没有实际执行a = “local var"。

记住以上两个栗子你就大概了解了命名空间和作用域的联系和作用。

    有的童鞋可能会想,既然可以访问各个命名空间,那么是否可以修改命名空间的变量值呢?答案是肯定的,这就要借助global和nonlocal语句。

    global语句,直接操作全局命名空间的变量。

栗子3:
a = 11
def f():
    def f2():
        global a    #在此作用域内可以操作全局作用域的变量
        a = 22
        print(a)
    f2()
f()
print(a)
#输出
#22
#22
    nonlocal语句,直接操作上层命名空间和本层命名空间的变量。
栗子4:
b = 11
def f():
    b = 22
    def f2():
        nonlocal b
        b = 33
        print(b)
    f2()
    print(b)
f()
print(b)
#输出
#33   
#33   #证明修改b有效
#11   #没有修改全局命名空间的b

    是不是感觉对命名空间和作用域了如指掌,我们再来看看类空间。

粒子5:
class Student(object):
    a = 123
    print(a)
    def func():
        print(a)
Student.func()
#输出:NameError: name 'a' is not defined
    这说明func函数根本就找不到a变量,但是不是按照LEGB规则不是能找到的吗?这里类空间有点特殊,类空间创建了一个命名空间但是却没有作用域,这就决定了谁都无法直接访问到它,除了在它的空间。我们把代码这样修改一下

栗子6:
class Student(object):
    a = 123
    print(a)
    def func():
        print(Student.a)
Student.func()
#输出:123
    这样就可以访问了,Student.a表示访问Student这个命名空间的a变量。这里说明类和函数的执行机制不相同,函数被创建后不会立刻被执行但是会创建对应的命名空间和作用域,但是类在创建后会被立刻执行,因为类是以后实例被创建的基础,这样可以提高效率和降低逻辑复杂度。
栗子7:
class Student(object): 
    a = 123
    print(a)
    print(Student.a)
#输出:NameError: name 'Student' is not defined
    因为Student类还没有被创建成功,只有把类的代码块执行完成后,类才算创建成功,后面的实例才能依次创建。

栗子8:
a = 1
class Student(object):
    print(a)
    a = 123
#输出:1
    没有报错,说明类空间的命名空间是动态创建的。

总结:

  1. 命名空间:名称到对象值的一个映射空间。
  2. 作用域:一个代码块区域,此代码块区域可以访问某些命名空间(访问就是基于作用域)。
  3. 作用域可以访问多个命名空间(层层访问),某个命名空间是依赖某个作用域生成(类空间是不是)。
  4. 命名空间的搜寻遵循LEGB-rule,依次搜寻。
  5. 作用域与命名空间的生成是在代码动态执行之前,不是在执行的过程中(如栗子2),但是类空间例外(如栗子8)。
  6. 类空间只会生成命名空间,不会生成作用域,因此在除在类空间以外的访问都需要通过类名的属性访问。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值