Python命名空间和作用域窥探.docx

上传人:b****5 文档编号:7437064 上传时间:2023-05-11 格式:DOCX 页数:13 大小:78.62KB
下载 相关 举报
Python命名空间和作用域窥探.docx_第1页
第1页 / 共13页
Python命名空间和作用域窥探.docx_第2页
第2页 / 共13页
Python命名空间和作用域窥探.docx_第3页
第3页 / 共13页
Python命名空间和作用域窥探.docx_第4页
第4页 / 共13页
Python命名空间和作用域窥探.docx_第5页
第5页 / 共13页
Python命名空间和作用域窥探.docx_第6页
第6页 / 共13页
Python命名空间和作用域窥探.docx_第7页
第7页 / 共13页
Python命名空间和作用域窥探.docx_第8页
第8页 / 共13页
Python命名空间和作用域窥探.docx_第9页
第9页 / 共13页
Python命名空间和作用域窥探.docx_第10页
第10页 / 共13页
Python命名空间和作用域窥探.docx_第11页
第11页 / 共13页
Python命名空间和作用域窥探.docx_第12页
第12页 / 共13页
Python命名空间和作用域窥探.docx_第13页
第13页 / 共13页
亲,该文档总共13页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

Python命名空间和作用域窥探.docx

《Python命名空间和作用域窥探.docx》由会员分享,可在线阅读,更多相关《Python命名空间和作用域窥探.docx(13页珍藏版)》请在冰点文库上搜索。

Python命名空间和作用域窥探.docx

Python命名空间和作用域窥探

NamespaceandScope(命名空间和作用域)

namespace

Namespace(只)是 从名字到对象的一个映射(amappingfromnametoobjects) 。

大部分namespace都是按Python中的字典来实现的。

有一些常见的namespace:

built-in中的集合( abs() 函数等)、一个模块中的全局变量等。

从某种意义上来说,一个对象(object)的所有属性(attribute)也构成了一个namespace。

在程序执行期间,可能(其实是肯定)会有多个名空间同时存在。

不同namespace的创建/销毁时间也不同。

此外,两个不同namespace中的两个相同名字的变量之间没有任何联系。

scope

有了namespace基础之后,让我们再来看看scope。

Scope是Python程序的一块文本区域(textualregion)。

在该文本区域中,对namespace是可以直接访问,而不需要通过属性来访问。

Scope是定义程序该如何搜索确切地“名字-对象”的名空间的层级关系。

(The“scope”inPythondefinesthe“hirerchylevel”inwhichwesearchnamespacesfor

certain“name-to-object”mappings.)

Tip

直接访问:

对一个变量名的引用会在所有namespace中查找该变量,而不是通过属性访问。

属性访问:

所有名字后加 . 的都认为是属性访问。

如 module_name.func_name ,需要指定 func_name 的名空间,属于属性访问。

而 abs(-1) , 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的搜索顺序:

Important

Local->Enclosing->Global->Built-in

怎么个意思呢?

当有一个变量在 local 域中找不到时,Python会找上一层的作用域,即 enclosing 域(该域不一定存在)。

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是不是?

1

2

3

cipher

0

1

如果多加一句呢?

Python

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

defouter():

    a=0

    b=1

 

    definner():

        printa

        printb

 

        #b+=1        #A

 

        b=4          #B

 

    inner()

 

outer()

结果又会是什么呢?

1

2

3

4

5

6

7

8

9

10

cipher

0

Traceback(mostrecentcalllast):

  File"a.py",line34,in

    outer()

  File"a.py",line32,inouter

    inner()

  File"a.py",line29,ininner

    printb

UnboundLocalError:

localvariable'b'referencedbeforeassignment

是不是很奇怪?

原因是这样的:

Python解释器执行到 inner() 中的 print b 时,发现有个变量 b 在当前作用域(local)中

无法找到该变量。

它继续尝试把整块代码解释完。

Bingo!

找到了。

那么 b 是属于 inner() 作用域的。

既然对变量 b 的赋值(声明)发生在 print 语句之后, print 语句执行时

变量 b 是还未被声明的,于是抛出错误:

变量在赋值前就被引用。

在这个例子中,只有A语句没有B语句也会导致同样的结果。

因为b+=1等同于b=b+1。

对于变量的作用域查找有了了解之后,还有两条很重要的规则:

Important

1.赋值语句通常隐式地会创建一个局部(local)变量,即便该变量名已存在于赋值语句发生的上一层作用域中;

2.如果没有 global 关键字声明变量,对一个变量的赋值总是认为该变量存在于最内层(innermost)的作用域中;

也就是说在作用域内有没有发生赋值是不一样的。

但是,在这点上,Python2和Python3又有不同, Pythonaccessnon-localvariable:

Python’sscopingrulesindicatethatafunctiondefinesanewscopelevel,

andanameisboundtoavalueinonlyonescopelevel–itisstaticallyscoped.

InPython2.x,itisnotpossibletomodifyanon-localvariable;

1)youhaveeitherread-onlyaccesstoaglobalornon-localvariable,

2)orread-writeaccesstoaglobalvariablebyusingthe global statement,

3)orread-writeaccesstoalocalvariable(bydefault).

InPython3.x,the nonlocal statementhasbeenintroducedwithasimilareffect

to global,butforanintermediatescope.

for 循环

为什么讲到作用域要说到 for 循环呢?

难道!

@#$%^&*()?

对于大部分语言(比如 C 语言)而言, for-loop 会引入一个新的作用域。

但Python有点一样却又不太一样。

让我们先来看个例子:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

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

...

0

1

2

3

4

5

6

7

8

9

>>>printi

9

>>>

有点不可思议是不是?

在 Python2.xfor语句 中是这么说的:

Thefor-loopmakesassignmentstothevariable(s)inthetargetlist.

Thisoverwritesallpreviousassignmentstothosevariableesincludingthosemadeinthesuiteofthefor-loop.

Thetargetlistisnotdeletedwhentheloopisfinished.

Butifthesequenceisempty,theywillnothavebeenassignedtoatalltheloop.

for 后面跟着的变量(targetlist)在循环结束后是不会被删除的,

但如果 for 循环的序列为空,这些变量是完全不会被赋值的。

这在Python中是个大坑啊。

避免这个坑的解决办法就是规范命名规范。

比如用于循环的变量尽量使用单字符。

在任何有疑议的情况可以直接将索引值初始化。

很不幸,Python3中这点没有改变。

ListComprehensionvs.GeneratorExpression

关于Python作用域这堂课已经上了很久了,我们先休息一下,说个题外话吧。

∙列表推导式(ListComprehension)

1

2

#Listcomprehension

[expressionforvariniterable]

简单的理解列表推导式:

Python

1

2

3

4

5

6

7

def_lc(arg):

    result=[]

    foriinarg:

        result.append(i*i)

    returnresult

 

=_lc(x)

o列表推导式会把所有数据都加载到内存。

适合“结果需要多次被使用”或者“需要使用list相关的方法(分片等)”等的情况。

∙生成器表达式(GeneratorExpression)

1

2

#Generatorexpression

(expressionforvariniterable)

简单的理解生成器表达式:

Python

1

2

3

4

5

def_ge(arg):

    foriinarg:

        yieldi*i

 

<expr_value>=_ge(x)

o使用生成器实现。

适合“数据量非常大或者无限”的情况。

它们的表现效果分别是这样的:

1

2

3

4

5

>>>[iforiinrange(10)]

[0,1,2,3,4,5,6,7,8,9]

 

>>>(iforiinrange(10))

at0x7fd5ab625b88>

Python作用域,我已经完全掌握了!

稍作小憩之后,看来大家对Python作用域很有信心了。

好的。

那我们来测试一下。

这是类(class)定义中的一个小问题:

Python

1

2

3

classA(object):

    a=3

    b=list(a+iforiinrange(10))

这段代码执行起来是不是跟你想的有点一样但又不那么一样呢?

1

2

3

4

5

6

7

8

9

cipher

Traceback(mostrecentcalllast):

  File"a.py",line3,in

    classA(object):

  File"a.py",line5,inA

    b=list(a+iforiinrange(10))

  File"a.py",line5,in

    b=list(a+iforiinrange(10))

NameError:

globalname'a'isnotdefined

刚刚总结的规则完全用不上啊!

“元芳,你怎么看?

真相只有一个:

class没有作用域(scope),但有一个局部的名空间(namespace),它并不构成一个作用域。

这意味着在类定义中的表达式可以访问该名空间。

但在类体(classbody)中,对 b 的赋值表达式中,该表达式引入了一个新的作用域,该作用域并不能访问类的名空间。

就像刚刚说的,函数会引入一个新的作用域。

比如说:

Python

1

2

3

4

classC(object):

    a=2

    deffoo(self):

        returna    #NameError:

name'a'isnotdefined,usereturnself.__class__.a

在Python2中,列表推导式没有引入一个新的作用域。

所以:

1

2

3

4

5

6

7

Python2.7.9(default,Jan252015,13:

42:

57)

[GCC4.2.1CompatibleAppleLLVM6.0(clang-600.0.56)]ondarwin

Type"help","copyright","credits"or"license"formoreinformation.

>>>[aforainrange(3)]

[0,1,2]

>>>printa

2

而对于Python2和Python3,生成器表达式都有引入新的作用域。

为了让列表推导式和生成器表达式的表现一致,

在Python3中,列表推导式也有引入一个新的作用域。

所以:

1

2

3

4

5

6

7

8

9

Python3.4.2(default,Apr252015,15:

59:

50)

[GCC4.2.1CompatibleAppleLLVM6.0(clang-600.0.57)]ondarwin

Type"help","copyright","credits"or"license"formoreinformation.

>>>[aforainrange(3)]

[0,1,2]

>>>print(a)

Traceback(mostrecentcalllast):

  File"",line1,in

NameError:

name'a'isnotdefined

解决方案

所以,要解决这个问题,有几种解决办法:

1.用生成器表达式

Python

1

b=[a+iforiinrange(10)]

1.用函数/lambda引入新的作用域

Python

1

b=(lambdaa:

((a+iforiinrange(10))))(a)

有没有开始怀疑人生怀疑理想?

附一份:

访问权限汇总表

Canaccessclassattributes

Python2

Python3

listcomp.iterable

Y

Y

listcomp.expression

Y

N

genexpr.iterable

Y

Y

genexpr.expression

N

N

dictcomp.iterable

Y

Y

dictcomp.expression

N

N

总结

本文介绍了Python中 namespace 和 scope 的区别,

以及复杂作用域的搜索规则( LEGB )。

此外,还介绍了一些常见的会创建scope的情况(函数定义,生成器表达式等),当然包括

了Python2和Python3中的不同实现。

Python中对于作用域的定义确实是个大问题,我并没有找到像 C 语言那样,

“代码块 {} 中定义的即是一个局部作用域”这样简洁的规则来清晰地表明

Python中作用域的 创建/销毁 的条件。

这篇文章的内容积压了很久,终于抽了点时间出来整理了下。

写的也有点没章法了,各位看官看得懂就看吧;看不懂多看几遍吧。

看望之后也提点啥建议意见之类的,好让后来人也能更快速简单的理解这个问题。

万一我理解错了呢?

欢迎探讨。

但有一点可以肯定,“这事儿还没完”。

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 自然科学 > 物理

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2