1. 命名空间html
1.1 什么是命名空间python
Namespace命名空间,也称名字空间,是从名字到对象的映射。Python中,大部分的命名空间都是由字典来实现的,可是本文的不会涉及命名空间的实现。命名空间的一大做用是避免名字冲突:程序员
1
2
3
4
5
|
def
fun1():
i
=
1
def
fun2():
i
=
2
|
同一个模块中的两个函数中,两个同名名字i之间绝没有任何关系,由于它们分属于不一样明明空间。编程
1.2 命名空间的种类编程语言
常见的命名空间有:ide
built-in名字集合,包括像abs()这样的函数,以及内置的异常名字等。一般,使用内置这个词表示这个命名空间-内置命名空间函数
模块全局名字集合,直接定义在模块中的名字,如类,函数,导入的其余模块等。一般,使用全局命名空间表示。ui
函数调用过程当中的名字集合,函数中的参数,函数体定义的名字等,在函数调用时被“激活”,构成了一个命名空间。一般,使用局部命名空间表示。spa
一个对象的属性集合,也构成了一个命名空间。但一般使用objname.attrname的间接方式访问属性,而不是直接访问,故不将其列入命名空间讨论。.net
类定义的命名空间,一般解释器进入类定义时,即执行到class ClassName:语句,会新建一个命名空间。(见官方对类定义的说明)
1.3 命名空间的生命周期
不一样类型的命名空间有不一样的生命周期:
内置命名空间,在Python解释器启动时建立,解释器退出时销毁;
全局命名空间,模块的全局命名空间在模块定义被解释器读入时建立,解释器退出时销毁;
局部命名空间,这里要区分函数以及类定义。函数的局部命名空间,在函数调用时建立,函数返回或者由未捕获的异常时销毁;类定义的命名空间,在解释器读到类定义建立,类定义结束后销毁。(关于类定义的命名空间,在类定义结束后销毁,但其实类对象就是这个命名空间内容的包装,见官方对类定义的说明)
2. 做用域
2.1 什么是做用域
做用域是Python的一块文本区域,这个区域中,命名空间能够被“直接访问”。这里的直接访问指的是试图在命名空间中找到名字的绝对引用(非限定引用)。这里有必要解释下直接引用和间接引用:
直接引用;直接使用名字访问的方式,如name,这种方式尝试在名字空间中搜索名字name。
间接引用;使用形如objname.attrname的方式,即属性引用,这种方式不会在命名空间中搜索名字attrname,而是搜索名字objname,再访问其属性。
2.2 与命名空间的关系
如今,命名空间持有了名字。做用域是Python的一块文本区域,即一块代码区域,须要代码区域引用名字(访问变量),那么必然做用域与命名空间之间就有了联系。
顾名思义,名字做用域就是名字能够影响到的代码文本区域,命名空间的做用域就是这个命名空间能够影响到的代码文本区域。那么也存在这样一个代码文本区域,多个命名空间能够影响到它。
做用域只是文本区域,其定义是静态的;而名字空间倒是动态的,只有随着解释器的执行,命名空间才会产生。那么,在静态的做用域中访问动态命名空间中的名字,形成了做用域使用的动态性。
那么,能够这样认为:
静态的做用域,是一个或多个命名空间按照必定规则叠加影响代码区域;运行时动态的做用域,是按照特定层次组合起来的命名空间。
在必定程度上,能够认为动态的做用域就是命名空间。在后面的表述中,我会把动态的做用域与其对应命名空间等同起来。
2.3 名字搜索规则
在程序中引用了一个名字,Python是怎样搜索到这个名字呢?
在程序运行时,至少存在三个命名空间能够被直接访问的做用域:
Local
首先搜索,包含局部名字的最内层(innermost)做用域,如函数/方法/类的内部局部做用域;
Enclosing
根据嵌套层次从内到外搜索,包含非局部(nonlocal)非全局(nonglobal)名字的任意封闭函数的做用域。如两个嵌套的函数,内层函数的做用域是局部做用域,外层函数做用域就是内层函数的 Enclosing做用域;
Global
倒数第二次被搜索,包含当前模块全局名字的做用域;
Built-in
最后被搜索,包含内建名字的最外层做用域。
程序运行时,LGB三个做用域是必定存在的,E做用域不必定存在;若程序是这样的:
1
2
|
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中一个名字只有在定义以后,才能引用。
1
|
print
(i)
|
直接引用未定义的名字i,按照搜索规则,在LGB三个做用域均没有搜索到名字i(LB相同命名空间)。抛出NameError异常:
1
2
3
4
|
Traceback (most recent call last):
File
"scope_test.py"
, line
15
,
in
<module>
print
(i)
NameError: name
'i'
is
not
defined
|
那对于这段代码呢?
1
2
3
4
5
6
|
def
try_to_define_name():
'''函数中定义了名字i,并绑定了一个整数对象1'''
i
=
1
try_to_define_name()
print
(i)
#引用名字i以前,调用了函数
|
在引用名字i以前,明明调用了函数,定义了名字i,但是仍是找不到这个名字:
1
2
3
4
|
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语句:
1
2
3
|
if
True
:
i
=
1
print
(i)
# output: 1,而不是NameError
|
if语句并不会引入新的做用域,因此名字绑定语句i = 1与print(i)是在同一个做用域中。
for语句:
1
2
3
|
for
i
in
range
(
6
):
pass
print
(i)
#output: 5,而不是NameError
|
for语句一样不会引入新的做用域,因此名字i的绑定和重绑定与print(i)在同一个做用域。这一点Python就比较坑了,所以写代码时切忌for循环名字要与其余名字不重名才行。
import语句:
1
2
3
4
5
6
|
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中的自由变量:
1
|
If a variable is used in a code block but not defined there, it is a free variable.
|
若是引用发生的代码块不是其定义的地方,它就是一个自由变量。专业一点,就是:
引用名字的做用域中没有这个名字,那这个名字就是自由名字
Note: “自由名字”只是做者YY的,并没获得普遍承认。
咱们已经了解了做用域有LEGB的层次,并按顺序搜索名字。按照搜索顺序,当低层做用域不存在待搜索名字时,引用高层做用域存在的名字,也就是自由名字:
[示例1]
1
2
3
4
5
|
def low_scope():
print(s)
s = 'upper scope'
low_scope()
|
很清楚,这段代码的输出是upper scope。
[示例2]
1
2
3
4
5
6
|
def low_scope():
s = 'lower scope'
s = 'upper scope'
low_scope()
print(s)
|
很遗憾,最后的打印语句没有按照期待打印出lower scope而是打印了upper scope。
1
|
A special quirk of Python is that – if no global statement is in effect – assignments to names always go into the innermost scope.
|
Python的一个怪癖是,若是没有使用global语句,对名字的赋值语句一般会影响最内层做用域。
即赋值语句影响局部做用域,赋值语句带来的影响是绑定或重绑定,可是在当前局部做用域的命名空间中,并无s这个名字,所以赋值语句在局部做用于定义了同名名字s,这与外层做用域中的s并不冲突,由于它们分属不一样命名空间。
这样,全局做用域的s没有被重绑定,结果就很好解释了。
当涉及可变对象时,状况又有所不一样了:
[示例3]
1
2
3
4
5
6
|
def low_scope():
l[0] = 2
l = [1, 2]
low_scope()
print(l) # [2, 2]
|
很遗憾,最后的打印语句没有按照期待输出[1, 2]而是输出了[2, 2]。
上一个例子的经验并不能运用在此,由于list做为一个可变对象,l[0] = 2并非对名字l的重绑定,而是对l的第一个元素的重绑定,因此没有新的名字被定义。所以在函数中成功更新了全局做用于中l所引用对象的值。
注意,下面的示例跟上面的是不同的:
[示例4]
1
2
3
4
5
6
|
def low_scope():
l = [2, 2]
l = [1, 2]
low_scope()
print(l) # [1, 2]
|
咱们能够用本节中示例1的方法解释它。
综上,能够认为:
自由变量可读不可写。
3.2 global和nonlocal
老是存在打破规则的需求:
在低层做用域中须要重绑定高层做用域名字,即经过自由名字重绑定。
因而global语句和nonlocal语句因运而生。
1
2
|
global_stmt ::= "global" identifier ("," identifier)*
The global statement is a declaration which holds for the entire current code block. It means that the listed identifiers are to be interpreted as globals. It would be impossible to assign to a global variable without global, although free variables may refer to globals without being declared global.
|
global语句是适用于当前代码块的声明语句。列出的标识符被解释为全局名字。虽然自由名字能够不被声明为global就能引用全局名字,可是不使用global关键字绑定全局名字是不可能的。
1
2
|
nonlocal_stmt ::= "nonlocal" identifier ("," identifier)*
The nonlocal statement causes the listed identifiers to refer to previously bound variables in the nearest enclosing scope excluding globals. This is important because the default behavior for binding is to search the local namespace first. The statement allows encapsulated code to rebind variables outside of the local scope besides the global (module) scope.
|
nonlocal语句使得列出的名字指向最近封闭函数中绑定的名字,而不是全局名字。默认的绑定行为会首先搜索局部做用域。nonlocal语句使得在内层函数中重绑定外层函数做用域中的名字成为可能,即便同名的名字存在于全局做用域。
经典的官方示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
def
scope_test():
def
do_local():
spam
=
'local spam'
def
do_nonlocal():
nonlocal spam
# 当外层做用域不存在spam名字时,nonlocal不能像global那样自做主张定义一个
spam
=
'nonlocal spam'
# 自由名字spam经nonlocal声明后,能够作重绑定操做了,可写的。
def
do_global():
global
spam
# 即便全局做用域中没有名字spam的定义,这个语句也能在全局做用域定义名字spam
spam
=
'global spam'
# 自有变量spam经global声明后,能够作重绑定操做了,可写的。
spam
=
'test spam'
do_local()
print
(
"After local assignment:"
, spam)
# After local assignment: test spam
do_nonlocal()
print
(
"After nonlocal assignment:"
, spam)
# After nonlocal assignment: nonlocal spam
do_global()
print
(
"After global assignment:"
, spam)
# After global assignment: nonlocal spam
scope_test()
print
(
"In global scope:"
, spam)
# In global scope: global spam
|
做者说不行nonlocal的邪:
1
2
3
4
5
6
7
8
9
10
11
|
def
nest_outter():
spam
=
'outer'
def
nest_inner():
nonlocal spam1
spam1
=
'inner'
nest_inner()
print
(spam)
nest_outter()
|
Output:
1
2
3
|
File "scope_test.py", line 41
nonlocal spam1
SyntaxError: no binding for nonlocal 'spam1' found
|
4. 一些坑
做者曾经自信满满认为透彻了解了Python的做用域,可是一大堆坑踩得触不及防。
4.1 坑1 - UnboundLocalError
1
2
3
4
5
6
|
def
test():
print
(i)
i
=
1
i
=
2
test()
|
Output:
1
2
3
4
5
6
|
Traceback (most recent call last):
File
"scope_test.py"
, line
42
,
in
<module>
test()
File
"scope_test.py"
, line
38
,
in
test
print
(i)
UnboundLocalError: local variable
'i'
referenced before assignment
|
其实忽略掉全局做用域中i = 2这条语句,均可以理解。
1
|
Usually, the local scope references the local names of the (textually) current function.
|
Python对局部做用域情有独钟,解释器执行到print(i),i在局部做用域没有。解释器尝试继续执行后面定义了名字i,解释器就认为代码在定义以前就是用了名字,因此抛出了这个异常。若是解释器解释完整个函数都没有找到名字i,那就会沿着搜索链LEGB往上找了,最后找不到抛出NameError异常。
4.2 坑2 - 类的局部做用域
1
2
3
4
5
6
7
8
9
10
|
class
Test(
object
):
i
=
1
def
test_print(
self
):
print
(i)
t
=
Test()
i
=
2
t.test_print()
|
我就问问你们,这个输出什么?
固然会出乎意料输出2了,特别是有其余语言经验的人会更加困惑。
上文强调过:
函数命名空间的生命周期是什么? 调用开始,返回或者异常结束,虽然示例中是调用的方法,但其本质是调用类的函数。
类命名空间的做用域是什么?类定义开始,类完成定义结束。
类定义开始时,建立新的属于类的命名空间,用做局部做用域。类定义完后,命名空间销毁,没有直接方法访问到类中的i了(除非经过间接访问的方式:Test.i)。
方法调用的本质是函数调用:
1
2
3
4
5
6
7
8
9
10
11
|
class
Test(
object
):
i
=
1
def
test_print(
self
):
print
(i)
t
=
Test()
i
=
2
# t.test_print()
Test.test_print(t)
# 方法调用最后转换成函数调用的方式
|
函数调用开始,其做用域与全局做用域有了上下级关系(L和G),函数中i做为自由名字,最后输出2。
所以,不能被类中数据成员和函数成员的位置迷惑,始终切记,Python中两种访问引用的方式:
直接引用:试图直接写名字name引用名字,Python按照搜索LEGB做用域的方式搜索名字。
间接引用:使用objname.attrname的方式引用名字attrname,Python不搜索做用域,直接去对象里找属性。
4.3 坑3 - 列表推导式的局部做用域
一个正常列表推导式:
1
2
3
|
a
=
1
b
=
[a
+
i
for
i
in
range
(
10
)]
print
(b)
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
如今把列表推导式放到类中:
1
2
3
4
5
6
7
8
|
class
Test(
object
):
a
=
1
b
=
[a
+
i
for
i
in
range
(
10
)]
print
(b)
def
test(
self
):
pass
|
Output:
1
2
3
4
5
6
7
8
|
Traceback (most recent call last):
File
"scope_test.py"
, line
15
,
in
<module>
class
Test(
object
):
File
"scope_test.py"
, line
18
,
in
Test
b
=
[a
+
i
for
i
in
range
(
10
)]
File
"scope_test.py"
, line
18
,
in
<listcomp>
b
=
[a
+
i
for
i
in
range
(
10
)]
NameError: name
'a'
is
not
defined
|
输出反馈名字a未定义。
上文强调过,解释器读取类定义开始class ClassName后,建立命名空间用做局部做用域。
语句a = 1,在这个局部做用域中定义了名字i
语句b = [a + i for i in rage(10)],列表推导式一样建立了一个局部做用域。这个做用域与类定义的局部做用域并无上下级关系,因此,天然没有任何直接访问名字a的方法。
Python中只有四种做用域:LEGB,由于类定义的局部做用域与列表推导式的局部做用域于不是嵌套函数关系,因此并不能构成Enclosing做用域关系。所以它们是两个独立的局部做用域,不能相互访问。
既然是两个独立局部做用域,那么上述例子就等同于:
1
2
3
4
5
6
7
8
|
def
test1():
i
=
1
def
test2():
print
(i)
test1()
test2()
|
期待在test2中访问test1的名字i,显然是不可行的。