Python进阶_关于命名空间与作用域(详解)
|
写在前面 如非特别说明,下文均基于Python3 命名空间与作用于跟名字的绑定相关性很大,可以结合另一篇介绍Python名字、对象及其绑定的文章。 1. 命名空间 1.1 什么是命名空间 Namespace命名空间,也称名字空间,是从名字到对象的映射。Python中,大部分的命名空间都是由字典来实现的,但是本文的不会涉及命名空间的实现。命名空间的一大作用是避免名字冲突: def fun1(): i = 1 def fun2(): i = 2 同一个模块中的两个函数中,两个同名名字i之间绝没有任何关系,因为它们分属于不同明明空间。 1.2 命名空间的种类 常见的命名空间有: built-in名字集合,包括像abs()这样的函数,以及内置的异常名字等。通常,使用内置这个词表示这个命名空间-内置命名空间 模块全局名字集合,直接定义在模块中的名字,如类,函数,导入的其他模块等。通常,使用全局命名空间表示。 函数调用过程中的名字集合,函数中的参数,函数体定义的名字等,在函数调用时被“激活”,构成了一个命名空间。通常,使用局部命名空间表示。 一个对象的属性集合,也构成了一个命名空间。但通常使用objname.attrname的间接方式访问属性,而不是直接访问,故不将其列入命名空间讨论。 类定义的命名空间,通常解释器进入类定义时,即执行到class ClassName:语句,会新建一个命名空间。(见官方对类定义的说明) 1.3 命名空间的生命周期 不同类型的命名空间有不同的生命周期: 内置命名空间,在Python解释器启动时创建,解释器退出时销毁; 全局命名空间,模块的全局命名空间在模块定义被解释器读入时创建,解释器退出时销毁; 局部命名空间,这里要区分函数以及类定义。函数的局部命名空间,在函数调用时创建,函数返回或者由未捕获的异常时销毁;类定义的命名空间,在解释器读到类定义创建,类定义结束后销毁。(关于类定义的命名空间,在类定义结束后销毁,但其实类对象就是这个命名空间内容的包装,见官方对类定义的说明) 2. 作用域 2.1 什么是作用域 作用域是Python的一块文本区域,这个区域中,命名空间可以被“直接访问”。这里的直接访问指的是试图在命名空间中找到名字的绝对引用(非限定引用)。这里有必要解释下直接引用和间接引用: 直接引用;直接使用名字访问的方式,如name,这种方式尝试在名字空间中搜索名字name。 间接引用;使用形如objname.attrname的方式,即属性引用,这种方式不会在命名空间中搜索名字attrname,而是搜索名字objname,再访问其属性。 2.2 与命名空间的关系 现在,命名空间持有了名字。作用域是Python的一块文本区域,即一块代码区域,需要代码区域引用名字(访问变量),那么必然作用域与命名空间之间就有了联系。 顾名思义,名字作用域就是名字可以影响到的代码文本区域,命名空间的作用域就是这个命名空间可以影响到的代码文本区域。那么也存在这样一个代码文本区域,多个命名空间可以影响到它。 那么,可以这样认为: 静态的作用域,是一个或多个命名空间按照一定规则叠加影响代码区域;运行时动态的作用域,是按照特定层次组合起来的命名空间。 在一定程度上,可以认为动态的作用域就是命名空间。在后面的表述中,我会把动态的作用域与其对应命名空间等同起来。 2.3 名字搜索规则 在程序中引用了一个名字,Python是怎样搜索到这个名字呢? 在程序运行时,至少存在三个命名空间可以被直接访问的作用域: Local Enclosing Global Built-in 程序运行时,LGB三个作用域是一定存在的,E作用域不一定存在;若程序是这样的: i = 1 print(i) 局部作用域在哪里呢?我们认为(Python Scopes And Namespaces): Usually,the local scope references the local names of the (textually) current function. Outside functions,the local scope references the same namespace as the global scope: the module's namespace. Class definitions place yet another namespace in the local scope. 一般地,局部作用域引用函数中定义的名字。函数之外,局部作用域和全局作用域引用同一个命名空间:模块的明星空间。然而类型的局部作用域引用了类定义新的命名空间。 Python按照以上L-E-G-B的顺序依次在四个作用域搜索名字。没有搜索到时,Python抛出NameError异常。 2.4 何时引入作用域我们知道: 我们知道: 在Python中一个名字只有在定义之后,才能引用。 print(i) 直接引用未定义的名字i,按照搜索规则,在LGB三个作用域均没有搜索到名字i(LB相同命名空间)。抛出NameError异常: Traceback (most recent call last): File "scope_test.py",line 15,in <module> print(i) NameError: name 'i' is not defined 那对于这段代码呢? def try_to_define_name(): '''函数中定义了名字i,并绑定了一个整数对象1''' i = 1 try_to_define_name() print(i) #引用名字i之前,调用了函数 在引用名字i之前,明明调用了函数,定义了名字i,可是还是找不到这个名字: Traceback (most recent call last): File "scope_test.py",line 20,in <module> print(i) #引用名字i之前,调用了函数 NameError: name 'i' is not defined 虽然定义了名字i,但是定义在了函数的局部作用域对应的局部命名空间中,按照LEGB搜索规则,在全局作用域中自然访问不到局部作用域;再者,函数调用结束后,这个命名空间被销毁了。 引用名字总是与作用域相关的,因此: 在Python中一个名字只有在定义之后,才能在合适的作用域引用。 那么,在定义名字时,就要注意名字定义的作用域了,以免定义后需要访问时却找不到。所以,了解Python在何时会引入新的作用域很有必要。一般来说,B,G两个作用域的引入在不能够通过代码操作的,能够通过语句引入的作用域只有E,L了。Python中引入新作用域的语句很有限,总的来说只有两类一个: 函数定义引入local作用域或者Enclosing作用域;本质上,lambda和生成器表达式也是函数,会引入新作用域。 类定义引入local作用域; 列表推导式引入local作用域,传说在python2中列表推导式不引入新的作用域 几个会让有其他高级语言经验的猿困惑的地方: if语句: if True: i = 1 print(i) # output: 1,而不是NameError if语句并不会引入新的作用域,所以名字绑定语句i = 1与print(i)是在同一个作用域中。 for语句: for i in range(6): pass print(i) #output: 5,而不是NameError for语句同样不会引入新的作用域,所以名字i的绑定和重绑定与print(i)在同一个作用域。这一点Python就比较坑了,因此写代码时切忌for循环名字要与其他名字不重名才行。 import语句: def import_sys(): '''import sys module''' import sys import_sys() print(sys.path) # NameError: name 'sys' is not defined 这个算非正常程序员的写法了,import语句在函数import_sys中将名字sys和对应模块绑定,那sys这个名字还是定义在局部作用域,跟上面的例子没有任务区别。要时刻切记Python的名字,对象,这个其他编程语言不一样,但是: 打破第一编程语言认知的第二门编程语言,才是值得去学的好语言。 3. 作用域应用 3.1 自由变量可读不可写 我不太想用“变量”这个词形容名字,奈何变量是家喻户晓了,Python中的自由变量: If a variable is used in a code block but not defined there,it is a free variable. 如果引用发生的代码块不是其定义的地方,它就是一个自由变量。专业一点,就是: 引用名字的作用域中没有这个名字,那这个名字就是自由名字 Note: “自由名字”只是作者YY的,并没得到广泛认可。 我们已经了解了作用域有LEGB的层次,并按顺序搜索名字。按照搜索顺序,当低层作用域不存在待搜索名字时,引用高层作用域存在的名字,也就是自由名字: [示例1] def low_scope(): print(s) s = 'upper scope' low_scope() 很清楚,这段代码的输出是upper scope。 [示例2] def low_scope(): s = 'lower scope' s = 'upper scope' low_scope() print(s) 很遗憾,最后的打印语句没有按照期待打印出lower scope而是打印了upper scope。 A special quirk of Python is that C if no global statement is in effect C assignments to names always go into the innermost scope. Python的一个怪癖是,如果没有使用global语句,对名字的赋值语句通常会影响最内层作用域。 当涉及可变对象时,情况又有所不同了: [示例3] def low_scope(): l[0] = 2 l = [1,2] low_scope() print(l) # [2,2] (编辑:安卓应用网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
