python的命名空间和作用域

时间:2022-03-20 22:45:08

name space是从名称(name)到对象(object)上的映射(map)。

当一个name映射到一个object上时,我们说这个name与这个object有绑定(bind)关系,或说这个name指向这个object。

每个name只对应一个object,而一个object可有多个名字(name)。(注意name不是object本身)

因为类的定义借用了命名空间,所以要深入的接触类,先得了解python的命名空间和作用域。

命名空间是从命名到对象的映射。 不同命名空间中的命名是没有任何联系的。

python中的任何一个“.”之后的命名为属性,比如:在x.pro表达式中,pro就是对象x的属性。

严格的说,从模块中引用命名空间是引用属性,modelname.function中,modelname是一个模型对象,function是它的一个属性,因此,模块中的属性和模块中的命名有直接的隐射关系,因为他们共享同一个命名空间。

属性是可以是可读或写的,对于可写的属性,可以进行赋值,比如:modelname.value = ‘value’,可写的属性可以用del删除。

比如:del modelname.value。

不同命名空间在不同的时刻创建,并且有不同的生存周期。包含内置命名的命名空间是在python的解释器启动时候就创建的,并且一直保留,不会被删除。

函数在被调用时候创建一个命名空间,而每一个递归也都拥有自己的命名空间。

作用域是python程序中一个命名空间可以直接访问的文本区域。在这里,直接访问的意思是查找命名时无需应用命名前缀。

尽管作用域是静态定义的,但是在使用的时候,他们都是动态的,每次执行的时候,至少有三个命名空间可以直接访问的作用域嵌套在一起。

他们分别是局部的,它在最里面,首先被搜索,然后再是当前命名空间作用域,最后是系统内置的作用域。

它们的搜索顺序是:局部命名空间->当前命名空间->内置命名空间

一般的局部命名空间是函数的内部,递归的内部。如果一个命名声明为全局,那么所有的赋值和引用都是直接针对包含模块全局命名的中级作用域。

作用域决定于源程序的文本。


Python 使用叫做名字空间的东西来记录变量的轨迹。

名字空间只是一个 dictionary ,它的键字就是变量名,它的值就是那些变量的值。

实际上,名字空间可以像 Python 的 dictionary 一样进行访问,一会儿我们就会看到。

在一个 Python 程序中的任何一个地方,都存在几个可用的名字空间。

每个函数都有着自已的名字空间,叫做局部名字空间,它记录了函数的变量,包括函数的参数和局部定义的变量。

每个模块拥有它自已的名字空间,叫全局名字空间,记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常量。

还有就是内置名字空间,任何模块均可访问它,它存放着内置的函数和异常。

当一行代码要使用变量 x 的值时,Python 会到所有可用的名字空间去查找变量,按照如下顺序:

  1. 局部名字空间――特指当前函数或类的方法。如函数定义了一局部变量 x,或一个参数 xPython 将使用它,然后停止搜索。
  2. 全局名字空间――特指当前的模块。如果模块定义了一个名为 x 的变量,函数或类,Python 将使用它然后停止搜索。
  3. 内置名字空间――对每个模块都是全局的。作为最后的尝试,Python 将假设 x 是内置函数或变量。

如果 Python 在这些名字空间找不到 x,它将放弃查找并引发一个 NameError 异常,同时传递 There is no variable named 'x' 这样一条信息,回到 例 3.18 “引用未赋值的变量”,您会看到一路上都有这样的信息。但是您并没有体会到 Python 在给出这样的错误之前做了多少的努力。

python的命名空间和作用域
Python 2.2 引入了一种略有不同但重要的改变,它会影响名字空间的搜索顺序:嵌套的作用域。 在 Python 2.2 版本之前,当您在一个嵌套函数或 lambda 函数中引用一个变量时,Python 会在当前 (嵌套的或 lambda) 函数的名字空间中搜索,然后在模块的名字空间。Python 2.2 将只在当前 (嵌套的或 lambda) 函数的名字空间中搜索,然后是在父函数的名字空间 中搜索,接着是模块的名字空间中搜索。Python 2.1 可 以两种方式工作,缺省地,按 Python 2.0 的方式工作。但是您可以把下面一行代码增加到您的模块头部,使您的模块工作起来像 Python 2.2 的方式:
from __future__ import nested_scopes

您是否为此而感到困惑?不要灰心!我敢说这一点非常酷。像 Python 中的许多事情一样,名字空间在运行时直接可以访问。怎么样?不错吧,局部名字空间可以通过内置的locals 函数来访问。全局 (模块级别) 名字空间可以通过内置的 globals 函数来访问。

例 8.10. locals 介绍

>>> def foo(arg): python的命名空间和作用域
... x = 1
... print locals()
...
>>> foo(7) python的命名空间和作用域
{'arg': 7, 'x': 1}
>>> foo('bar') python的命名空间和作用域
{'arg': 'bar', 'x': 1}
python的命名空间和作用域 函数 foo 在它的局部名字空间中有两个变量:arg (它的值是被传入函数的) 和 x (它是在函数里定义的)。
python的命名空间和作用域 locals 返回一个名字/值对的 dictionary。这个 dictionary 的键字是字符串形式的变量名字,dictionary 的值是变量的实际值。所以用 7 来调用 foo,会打印出包含函数两个局部变量的 dictionary:arg (7) 和 x (1)。
python的命名空间和作用域 回想一下,Python 有动态数据类型,所以您可以非常容易地传递给 arg 一个字符串,这个函数 (和对 locals 的调用) 将仍然很好的工作。locals 可以用于所有类型的变量。

locals 对局部 (函数) 名字空间做了些什么,globals 就对全局 (模块) 名字空间做了什么。然而 globals 更令人兴奋,因为一个模块的名字空间是更令人兴奋的。[6] 模块的名字空间不仅仅包含了模块级的变量和常量,还包括了所有在模块中定义的函数和类。除此以外,它还包括了任何被导入到模块中的东西。

回想一下 from module import 和 import module 之间的不同。使用 import module,模块自身被导入,但是它保持着自已的名字空间,这就是为什么您需要使用模块名来访问它的函数或属性:module.function 的原因。但是使用 from module import,实际上是从另一个模块中将指定的函数和属性导入到您自己的名字空间,这就是为什么您可以直接访问它们却不需要引用它们所来源的模块。使用 globals 函数,您会真切地看到这一切的发生。

例 8.11. globals 介绍

看看下面列出的在文件 BaseHTMLProcessor.py 尾部的代码块:


if
__name__ == "__main__":
for k, v in globals().items(): python的命名空间和作用域
print k, "=", v
python的命名空间和作用域 不要被吓坏了,想想以前您已经全部都看到过了。globals 函数返回一个 dictionary,我们使用 items 方法和多变量赋值遍历 dictionary。在这里唯一的新东西就是globals 函数。

现在从命令行运行这个脚本,会得到下面的输出 (注意您的输出可能有略微的不同,这依赖于您的系统平台和所安装的 Python 版本):

c:\docbook\dip\py> python BaseHTMLProcessor.py
SGMLParser = sgmllib.SGMLParser                python的命名空间和作用域
htmlentitydefs = <module 'htmlentitydefs' from 'C:\Python23\lib\htmlentitydefs.py'> python的命名空间和作用域
BaseHTMLProcessor = __main__.BaseHTMLProcessor python的命名空间和作用域
__name__ = __main__ python的命名空间和作用域
... rest of output omitted for brevity...
python的命名空间和作用域 我们使用了 from module import 把 SGMLParser 从 sgmllib 中导入。也就是说它被直接导入到我们的模块名字空间了,就是这样。
python的命名空间和作用域 把上面的例子和 htmlentitydefs 对比一下,它是用 import 被导入的。也就是说 htmlentitydefs 模块本身被导入了名字空间,但是定义在 htmlentitydefs 之中的 entitydefs 变量却没有。
python的命名空间和作用域 这个模块只定义一个类,BaseHTMLProcessor,不错。注意这儿的值就是类本身,不是一个特别的类实例。
python的命名空间和作用域 记得 if __name__ 技巧吗?当运行一个模块时 (相对于从另外一个模块中导入而言),内置的 __name__ 是一个特殊值 __main__。因为我们是把这个模块当作脚本从命令来运行的,故 __name__ 值为 __main__,这就是为什么我们这段简单地打印 globals 的代码可以执行的原因。
python的命名空间和作用域
使用 locals 和 globals 函数,通过提供变量的字符串名字您可以动态地得到任何变量的值。这种方法提供了这样的功能:getattr 函数允许您通过提供函数的字符串名来动态地访问任意的函数。

在 locals 与 globals 之间有另外一个重要的区别,您应该在它困扰您之前就了解它。它无论如何都会困扰您的,但至少您还会记得曾经学习过它。

例 8.12. locals 是只读的,globals 不是


def
foo(arg):
x = 1
print locals() python的命名空间和作用域
locals()["x"] = 2 python的命名空间和作用域
print "x=",x python的命名空间和作用域

z = 7
print "z=",z
foo(3)
globals()["z"] = 8 python的命名空间和作用域
print
"z=",z python的命名空间和作用域
python的命名空间和作用域 因为使用 3 来调用 foo,会打印出 {'arg': 3, 'x': 1}。这个应该没什么奇怪的。
python的命名空间和作用域 locals 是一个返回 dictionary 的函数,这里您在 dictionary 中设置了一个值。您可能认为这样会改变局部变量 x 的值为 2,但并不会。locals 实际上没有返回局部名字空间,它返回的是一个拷贝。所以对它进行改变对局部名字空间中的变量值并无影响。
python的命名空间和作用域 这样会打印出 x= 1,而不是 x= 2
python的命名空间和作用域 在有了对 locals 的经验之后,您可能认为这样不会 改变 z 的值,但是可以。由于 Python 在实现过程中内部有所区别 (关于这些区别我宁可不去研究,因为我自已还没有完全理解) ,globals 返回实际的全局名字空间,而不是一个拷贝:与 locals 的行为完全相反。所以对 globals 所返回的 dictionary 的任何的改动都会直接影响到全局变量。
python的命名空间和作用域 这样会打印出 z= 8,而不是 z= 7

1. import 实际上是python虚拟机把当前的globals()和locals()传进__builtins__.__import__内置函数了,所以实际上干活的是那个__import__函数!

 

2. import对命名空间的影响

1)如果是python的内置模块,例如os模块。这些模块是随着python虚拟机启动而加载进来的,但是并没有暴露出来。我们可以通过dir()命令查看当前命名空间

Python代码  python的命名空间和作用域
  1. >>> dir()  
  2. ['__builtins__''__doc__''__name__']  

 可以看到,并没有看到像os,sys等模块。

但是我们如果执行import sys后,那就有了。

Python代码  python的命名空间和作用域
  1. >>> import sys  
  2. >>> dir()  
  3. ['__builtins__''__doc__''__name__''sys']  

 通过sys.modules这个字典(key: 模块名;value: 模块的路径),我们可以查看模块的信息。再通过id函数,我们可以知道两个sys模块是否为同一个模块,如下:

Python代码  python的命名空间和作用域
  1. >>> id(sys)  
  2. 135708788  
  3. >>> id(sys.modules['sys'])  
  4. 135708788  

 

2)import只影响当前模块的命名空间

例如a.py里面有一句import bb,  而bb.py里面有一句import os,那么可以这样查看

Python代码  python的命名空间和作用域
  1. >>> import a  
  2. >>> dir()  
  3. ['__builtins__''__doc__''__name__''a']  
  4. >>> dir(a)  
  5. ['__builtins__''__doc__''__file__''__name__''bb']  
  6. >>> dir(a.bb)  
  7. ['__builtins__''__doc__''__file__''__name__''os']  
 

3. import package

首先合法的package必须含有一个__init__.py文件,package可以包含0个或多个module(py文件)。假设aa文件夹中有test.py文件和bb文件夹,bb文件夹里面有c.py。那么当import aa.bb.c的时候,究竟对命名空间产生什么影响呢?

Python代码  python的命名空间和作用域
  1. >>> import aa.bb.c  
  2. >>> dir()  
  3. ['__builtins__''__doc__''__name__''aa']  
  4. >>> import sys   
  5. >>> for k,v in sys.modules.items():  
  6. ...     print k,'\t',v  
  7. ...   
  8. <span style="color: #ff0000;">aa    <module 'aa' from 'aa/__init__.pyc'></span>  
  9.   
  10.   
  11.   
  12. copy_reg    <module 'copy_reg' from '/usr/local/lib/python2.5/copy_reg.pyc'>  
  13. __main__    <module '__main__' (built-in)>  
  14. site    <module 'site' from '/usr/local/lib/python2.5/site.pyc'>  
  15. __builtin__     <module '__builtin__' (built-in)>  
  16. encodings   <module 'encodings' from '/usr/local/lib/python2.5/encodings/__init__.pyc'>  
  17. encodings.encodings     None  
  18. <span style="color: #ff0000;">aa.bb.c   <module 'aa.bb.c' from 'aa/bb/c.pyc'></span>  
  19.   
  20.   
  21.   
  22. posixpath   <module 'posixpath' from '/usr/local/lib/python2.5/posixpath.pyc'>  
  23. errno   <module 'errno' (built-in)>  
  24. encodings.codecs    None  
  25. encodings.latin_1   <module 'encodings.latin_1' from '/usr/local/lib/python2.5/encodings/latin_1.pyc'>  
  26. os.path     <module 'posixpath' from '/usr/local/lib/python2.5/posixpath.pyc'>  
  27. _codecs     <module '_codecs' (built-in)>  
  28. stat    <module 'stat' from '/usr/local/lib/python2.5/stat.pyc'>  
  29. zipimport   <module 'zipimport' (built-in)>  
  30. warnings    <module 'warnings' from '/usr/local/lib/python2.5/warnings.pyc'>  
  31. encodings.types     None  
  32. UserDict    <module 'UserDict' from '/usr/local/lib/python2.5/UserDict.pyc'>  
  33. sys     <module 'sys' (built-in)>  
  34. codecs  <module 'codecs' from '/usr/local/lib/python2.5/codecs.pyc'>  
  35. readline    <module 'readline' from '/usr/local/lib/python2.5/lib-dynload/readline.so'>  
  36. types   <module 'types' from '/usr/local/lib/python2.5/types.pyc'>  
  37. _types  <module '_types' (built-in)>  
  38. signal  <module 'signal' (built-in)>  
  39. linecache   <module 'linecache' from '/usr/local/lib/python2.5/linecache.pyc'>  
  40. posix   <module 'posix' (built-in)>  
  41. encodings.aliases   <module 'encodings.aliases' from '/usr/local/lib/python2.5/encodings/aliases.pyc'>  
  42. <span style="color: #ff0000;">aa.bb     <module 'aa.bb' from 'aa/bb/__init__.pyc'></span>  
  43.   
  44.   
  45.   
  46. exceptions  <module 'exceptions' (built-in)>  
  47. os  <module 'os' from '/usr/local/lib/python2.5/os.pyc'>  
 

可以看到只是一句import aa.bb.c,却把aa,aa.bb,aabb.c全部加入了当前命名空间。这种做法是为了防止重名的吧,因为如果不加前缀的话,其他包里面有重名的模块就不能识别了!原理是python现在当前命名空间查找符号‘aa’对应的object,然后再在它的命名空间查找符号'bb',然后在bb的属性(命名空间)里面寻找c,所以,需要把aa,bb都加载进来。不过这些都是只加载一次的,不信的话,大家可以试一下再import aa.bb.d这样的模块,同时预先在aa文件夹中的

__init__.py文件中输入print ‘hello’,hello只在第一次import aa.bb.c的时候输出!

 

4.from与import

1)例如:from aa import bb

Python代码  python的命名空间和作用域
  1. >>> from aa import bb  
  2. >>> dir()  
  3. ['__builtins__''__doc__''__name__''bb']  
  4. >>> import sys   
  5. >>> sys.modules['bb']  
  6. Traceback (most recent call last):  
  7.   File "<stdin>", line 1in <module>  
  8. KeyError: 'bb'  
  9. >>> sys.modules['aa.bb']  
  10. <module 'aa.bb' from 'aa/bb/__init__.pyc'>  
  11. >>> bb  
  12. <module 'aa.bb' from 'aa/bb/__init__.pyc'>  
  13. >>> aa.bb  
  14. Traceback (most recent call last):  
  15.   File "<stdin>", line 1in <module>  
  16. NameError: name 'aa' is not defined  

 from import只是把import后面的名字引入了命名空间,让我们可以更方便的 使用而已,但是内部实际上还是带有模块名的。import 。。。as。。。也是同样的道理了。

 

2)from 。。。import *

这种形式的import是不推荐的,因为很容易污染命名空间。但是也可以了解一下。

首先如果package中的__init__.py文件并没有声明__all__ 的值的话,from package import * 是没有用的。因为python源码中的import_from_all这个函数估计会去读__all__的值,然后再import。

而这种形式的import对命名空间的影响又是怎样的呢?

 答案是和import aa.bb.c那种一样的。