Python命名空间和作用域窥探Word文档格式.docx
《Python命名空间和作用域窥探Word文档格式.docx》由会员分享,可在线阅读,更多相关《Python命名空间和作用域窥探Word文档格式.docx(13页珍藏版)》请在冰豆网上搜索。
,
abs
属于直接访问。
两者之间有什么联系呢?
Important
在Python中,scope是由namespace按特定的层级结构组合起来的。
scope一定是namespace,但namespace不一定是scope.
LEGB-rule
在一个Python程序运行中,至少有4个scopes是存在的。
直接访问一个变量可能在这四个namespace中逐一搜索。
∙Local(innermost)
包含局部变量。
比如一个函数/方法内部。
∙Enclosing
包含了非局部(non-local)也非全局(non-global)的变量。
比如两个嵌套函数,内层函数可能搜索外层函数的namespace,但该namespace对内层函数而言既非局部也非全局。
∙Global(next-to-last)
当前脚本的最外层。
比如当前模块的全局变量。
∙Built-in(outtermost)
Python
__builtin__
模块。
包含了内建的变量/关键字等。
那么,这么多的作用域,Python是按什么顺序搜索对应作用域的呢?
著名的”LEGB-rule”,即scope的搜索顺序:
Local->
Enclosing->
Global->
Built-in
怎么个意思呢?
当有一个变量在
local
域中找不到时,Python会找上一层的作用域,即
enclosing
域(该域不一定存在)。
域还找不到的时候,再往上一层,搜索模块内的
global
域。
最后,会在
built-in
域中搜索。
对于最终没有搜索到时,Python会抛出一个
NameError
异常。
作用域可以嵌套。
比如模块导入时。
这也是为什么不推荐使用
from
a_module
import
*
的原因,导入的变量可能被当前模块覆盖。
Assignmentrule
看似python作用域到此为止已经很清晰了,让我们再看一段代码:
Python
1
2
3
4
5
6
7
8
9
10
11
defouter():
a=0
b=1
definner():
printa
printb
inner()
outer()
你觉得结果是什么呢?
Soeasy是不是?
cipher<
ahref="
~/Development/Workspace/test_Python$python2a.py
如果多加一句呢?
12
13
14
15
#b+=1
#A
b=4
#B
结果又会是什么呢?
Traceback(mostrecentcalllast):
File"
a.py"
line34,in<
module>
line32,inouter
line29,ininner
UnboundLocalError:
localvariable'
b'
referencedbeforeassignment
是不是很奇怪?
原因是这样的:
Python解释器执行到
inner()
中的
print
b
时,发现有个变量
在当前作用域(local)中
无法找到该变量。
它继续尝试把整块代码解释完。
Bingo!
找到了。
那么
是属于
作用域的。
既然对变量
的赋值(声明)发生在
语句之后,
语句执行时
变量
是还未被声明的,于是抛出错误:
变量在赋值前就被引用。
在这个例子中,只有A语句没有B语句也会导致同样的结果。
因为b+=1等同于b=b+1。
对于变量的作用域查找有了了解之后,还有两条很重要的规则:
1.赋值语句通常隐式地会创建一个局部(local)变量,即便该变量名已存在于赋值语句发生的上一层作用域中;
2.如果没有
关键字声明变量,对一个变量的赋值总是认为该变量存在于最内层(innermost)的作用域中;
也就是说在作用域内有没有发生赋值是不一样的。
但是,在这点上,Python2和Python3又有不同,
Pythonaccessnon-localvariable:
Python’sscopingrulesindicatethatafunctiondefinesanewscopelevel,
andanameisboundtoavalueinonlyonescopelevel–itisstaticallyscoped.
…
InPython2.x,itisnotpossibletomodifyanon-localvariable;
1)youhaveeitherread-onlyaccesstoaglobalornon-localvariable,
2)orread-writeaccesstoaglobalvariablebyusingthe
statement,
3)orread-writeaccesstoalocalvariable(bydefault).
InPython3.x,the
nonlocal
statementhasbeenintroducedwithasimilareffect
to
global,butforanintermediatescope.
for
循环
为什么讲到作用域要说到
循环呢?
难道!
@#$%^&
*()?
?
对于大部分语言(比如
C
语言)而言,
for-loop
会引入一个新的作用域。
但Python有点一样却又不太一样。
让我们先来看个例子:
16
17
18
19
CipherChen@CIPHERC~/Development/Workspace/test_python$python2
Python2.7.9(default,Jan252015,13:
42:
57)
[GCC4.2.1CompatibleAppleLLVM6.0(clang-600.0.56)]ondarwin
Type"
help"
"
copyright"
credits"
or"
license"
formoreinformation.
>
foriinrange(10):
printi
...
有点不可思议是不是?
在
Python2.xfor语句
中是这么说的:
Thefor-loopmakesassignmentstothevariable(s)inthetargetlist.
Thisoverwritesallpreviousassignmentstothosevariableesincludingthosemadeinthesuiteofthefor-loop.
Thetargetlistisnotdeletedwhentheloopisfinished.
Butifthesequenceisempty,theywillnothavebeenassignedtoatalltheloop.
后面跟着的变量(targetlist)在循环结束后是不会被删除的,
但如果
循环的序列为空,这些变量是完全不会被赋值的。
这在Python中是个大坑啊。
避免这个坑的解决办法就是规范命名规范。
比如用于循环的变量尽量使用单字符。
在任何有疑议的情况可以直接将索引值初始化。
很不幸,Python3中这点没有改变。
ListComprehensionvs.GeneratorExpression
关于Python作用域这堂课已经上了很久了,我们先休息一下,说个题外话吧。
∙列表推导式(ListComprehension)
#Listcomprehension
[expressionforvariniterable]
简单的理解列表推导式:
def_lc(arg):
result=[]
foriinarg:
result.append(i*i)
returnresult
<
expr_value>
=_lc(x)
o列表推导式会把所有数据都加载到内存。
适合“结果需要多次被使用”或者“需要使用list相关的方法(分片等)”等的情况。
∙生成器表达式(GeneratorExpression)
#Generatorexpression
(expressionforvariniterable)
简单的理解生成器表达式:
def_ge(arg):
yieldi*i
&
lt;
expr_value&
gt;
=_ge(x)
o使用生成器实现。
适合“数据量非常大或者无限”的情况。
它们的表现效果分别是这样的:
[iforiinrange(10)]
[0,1,2,3,4,5,6,7,8,9]
(iforiinrange(10))
generatorobject<
genexpr>
at0x7fd5ab625b88>
Python作用域,我已经完全掌握了!
稍作小憩之后,看来大家对Python作用域很有信心了。
好的。
那我们来测试一下。
这是类(class)定义中的一个小问题:
classA(object):
a=3
b=list(a+iforiinrange(10))
这段代码执行起来是不是跟你想的有点一样但又不那么一样呢?
~/Development/Workspace/test_Python$pythona.py
line3,in<
line5,inA
line5,in<
NameError:
globalname'
a'
isnotdefined
刚刚总结的规则完全用不上啊!
!
“元芳,你怎么看?
”
真相只有一个:
class没有作用域(scope),但有一个局部的名空间(namespace),它并不构成一个作用域。
这意味着在类定义中的表达式可以访问该名空间。
但在类体(classbody)中,对
的赋值表达式中,该表达式引入了一个新的作用域,该作用域并不能访问类的名空间。
就像刚刚说的,函数会引入一个新的作用域。
比如说:
classC(object):
a=2
deffoo(self):
returna
#NameError:
name'
isnotdefined,usereturnself.__class__.a
在Python2中,列表推导式没有引入一个新的作用域。
所以:
[aforainrange(3)]
[0,1,2]
printa
而对于Python2和Python3,生成器表达式都有引入新的作用域。
为了让列表推导式和生成器表达式的表现一致,
在Python3中,列表推导式也有引入一个新的作用域。
Python3.4.2(default,Apr252015,15:
59:
50)
[GCC4.2.1CompatibleAppleLLVM6.0(clang-600.0.57)]ondarwin
print(a)
stdin>
"
line1,in<
解决方案
所以,要解决这个问题,有几种解决办法:
1.用生成器表达式
b=[a+iforiinrange(10)]
1.用函数/lambda引入新的作用域
b=(lambdaa:
((a+iforiinrange(10))))(a)
有没有开始怀疑人生怀疑理想?
附一份:
访问权限汇总表
Canaccessclassattributes
Python2
Python3
listcomp.iterable
Y
listcomp.expression
N
genexpr.iterable
genexpr.expression
dictcomp.iterable
dictcomp.expression
总结
本文介绍了Python中
namespace
和
scope
的区别,
以及复杂作用域的搜索规则(
LEGB
)。
此外,还介绍了一些常见的会创建scope的情况(函数定义,生成器表达式等),当然包括
了Python2和Python3中的不同实现。
Python中对于作用域的定义确实是个大问题,我并没有找到像
语言那样,
“代码块
{}
中定义的即是一个局部作用域”这样简洁的规则来清晰地表明
Python中作用域的
创建/销毁
的条件。
这篇文章的内容积压了很久,终于抽了点时间出来整理了下。
写的也有点没章法了,各位看官看得懂就看吧;
看不懂多看几遍吧。
看望之后也提点啥建议意见之类的,好让后来人也能更快速简单的理解这个问题。
万一我理解错了呢?
欢迎探讨。
但有一点可以肯定,“这事儿还没完”。