Let the word of Christ dwell in you richly in all wisdom; teaching and admonishing one another in psalms and hymns and spiritual songs, singing with grrace in your hearts tto the Lord. And whatsoever ye do in word or deed, do all in the name of the Lord Jesus, giving thanks to God and the Father by him. (COLOSSIANS 3:14-15)

命名空间

命名空间,英语叫做namespace,是很多编程语言中都会出现的术语。所以,有必要了解。

全局变量和局部变量

全局变量和局部变量,是理解命名空间的起始。

下面是一段代码,注意这段代码中有一个函数funcx(),这个函数里面有一个x=9,在函数的前面也有一个x=2

x = 2

def funcx():
    x = 9
    print "this x is in the funcx:-->", x        #Python 3请自动修改为print(),下同,从略

funcx()
print "--------------------------"
print "this x is out of funcx:-->", x

这段代码输出的结果是什么呢?看:

this x is in the funcx:--> 9
--------------------------
this x is out of funcx:--> 2

从输出中可以看出,运行funcx(),输出了funcx()里面的变量x引用的对象9;然后执行代码中的最后一行print "this x is out of funcx:-->",x

特别要关注的是,前一个x输出的是函数内部的变量x;后一个x输出的是函数外面的变量x。两个变量彼此没有互相影响,虽然都是x。两个x各自在各自的领域内起到作用。

把那个只在函数体内(某个范围内)起作用的变量称之为局部变量

有局部,就有对应的全部,在汉语中,全部变量,似乎有歧义,幸亏汉语丰富,于是又取了一个名词:全局变量

x = 2
def funcx():
    global x    #跟上面函数的不同之处
    x = 9
    print "this x is in the funcx:-->",x

funcx()
print "--------------------------"
print "this x is out of funcx:-->",x

以上两段代码的不同之处在于,后者在函数内多了一个global x,这句话的意思是在声明x是全局变量,也就是说这个x跟函数外面的那个x同一个,接下来通过x=9将x的引用对象变成了9。所以,就出现了下面的结果。

this x is in the funcx:--> 9
--------------------------
this x is out of funcx:--> 9

好似全局变量能力很强悍,能够统统率函数内外。但是,要注意,这个东西要慎重使用,因为往往容易带来变量的混乱。内外有别,在程序中一定要注意的。

局部变量和全局变量是在不同的范围内起作用。所谓的不同范围,就是变量产生作用的区域,简称作用域。

作用域

所谓作用域,是“名字与实体的绑定保持有效的那部分计算机程序”(引自《维基百科》),用直白的方式说,就是程序中变量与对象存在关联的那段程序。如果用前面的例子说明,x = 2x = 9是处在两个不同的作用域中。

通常,把作用域还分为静态作用域和动态作用域两种,虽然Python是所谓的动态语言(不很严格的划分),但它的作用域属于静态作用域,意即Python中变量的作用域由它在程序中的位置决定,如同上面例子中的x = 9位于函数体内,它的作用域和x = 2就不同。

那么,Python的作用域是怎么划分的呢?可以划分为四个层级:

  1. Local:局部作用域,或称本地作用域
  2. Enclosing:嵌套作用域
  3. Global:全局作用域
  4. Built-in:内建作用域

对于一个变量,Python也是按照上述从前到后的顺序,在不同作用域中查找。在刚才的例子中,对于x,首先搜索的是函数体内的本地作用域,然后是函数体外的全局作用域。

#!/usr/bin/env python
#coding:utf-8

def outer_foo():
    a = 10
    def inner_foo():
        a = 20
        print "inner_foo,a=", a      #a=20
                                                           #Python 3的读者,请自行修改为print()函数形式,下同,从略
    inner_foo()
    print "outer_foo,a=", a          #a=10

a = 30
outer_foo()
print "a=", a                #a=30

运行结果

inner_foo,a= 20
outer_foo,a= 10
a= 30

仔细观察上述程序和运行结果,你会看出对变量在不同范围进行搜索的规律的。

在Python程序中,变量的作用域是有在函数、类中才能被改变,或者说,如果不是在函数或者类中,比如在循环或者条件语句中,变量都是在同一层级的作用域中。可以再次参考上述的示例,并且可以在上述示例中修改,检测你的理解。

命名空间

“命名空间是对作用域的一种特殊的抽象”(引自《维基百科》)。下面就继续理解这种抽象。

先看来自《维基百科》的定义,这个定义通俗易懂。

命名空间(英语:Namespace)表示标识符(identifier)的可见范围。一个标识符可在多个命名空间中定义,它在不同命名空间中的含义是互不相干的。这样,在一个新的命名空间中可定义任何标识符,它们不会与任何已有的标识符发生冲突,因为已有的定义都处于其它命名空间中。

例如,设Bill是X公司的员工,工号为123,而John是Y公司的员工,工号也是123。由于两人在不同的公司工作,可以使用相同的工号来标识而不会造成混乱,这里每个公司就表示一个独立的命名空间。如果两人在同一家公司工作,其工号就不能相同了,否则在支付工资时便会发生混乱。

这一特点是使用命名空间的主要理由。在大型的计算机程序或文档中,往往会出现数百或数千个标识符。命名空间(或类似的方法,见“命名空间的模拟”一节)提供一隱藏區域標識符的機制。通过将逻辑上相关的标识符组织成相应的命名空间,可使整个系统更加模块化。

在编程语言中,命名空间是对作用域的一种特殊的抽象,它包含了处于该作用域内的标识符,且本身也用一个标识符来表示,这样便将一系列在逻辑上相关的标识符用一个标识符组织了起来。许多现代编程语言都支持命名空间。在一些编程语言(例如C++和Python)中,命名空间本身的标识符也属于一个外层的命名空间,也即命名空间可以嵌套,构成一个命名空间树,树根则是无名的全局命名空间。

函数和类的作用域可被視作隱式命名空间,它們和可見性、可訪問性和对象生命周期不可分割的联系在一起。

在这段定义中,已经非常清晰地描述了命名空间的含义,特别是如果你已经理解了作用域之后,对命名空间就没有什么陌生感了。

为了凸显命名空间之对Python程序员的重要价值,请在交互模式下,输入:import this,可以看到:

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

这就是所谓《python之禅》,请看最后一句: Namespaces are one honking great idea -- let's do more of those!

简而言之,命名空间是从所定义的命名到对象的映射集合。

不同的命名空间,可以同时存在,当彼此相互独立互不干扰。

命名空间因为对象的不同,也有所区别,可以分为如下几种:

  1. 本地命名空间(Function&Class: Local Namespaces):模块中有函数或者类,每个函数或者类所定义的命名空间就是本地命名空间。如果函数返回了结果或者抛出异常,则本地命名空间也结束了。
  2. 全局命名空间(Module:Global Namespaces):每个模块创建它自己所拥有的全局命名空间,不同模块的全局命名空间彼此独立,不同模块中相同名称的命名空间,也会因为模块的不同而不相互干扰。
  3. 内置命名空间(Built-in Namespaces):Python运行起来,它们就存在了。内置函数的命名空间都属于内置命名空间,所以,我们可以在任何程序中直接运行它们,比如前面的id(),不需要做什么操作,拿过来就直接使用了。

从网上盗取了一张图,展示一下上述三种命名空间的关系

那么程序在查询上述三种命名空间的时候,就按照从里到外的顺序,即:Local Namespaces --> Global Namesspaces --> Built-in Namesspaces

>>> def foo(num,str):
...     name = "qiwsir"
...     print locals()
... 
>>> foo(221,"qiwsir.github.io")
{'num': 221, 'name': 'qiwsir', 'str': 'qiwsir.github.io'}
>>> 

这是一个访问本地命名空间的方法,用print locals() 完成,从这个结果中不难看出,所谓的命名空间中的数据存储结构和字典是一样的。

根据习惯,读者一定已经猜测到了,如果访问全局命名空间,可以使用 print globals()

对于不同的命名空间,除了存在查找的顺序之外,还有不同的生命周期,即什么时候它存在,什么时候它消失了。对此,在理解上比较简单,那就是哪个部分被读入内存,它相应的命名空间就存在了。比如程序启动,内置命名空间就创建,一直到程序结束;而其它的,比如本地命名空间,就是在函数调用时开始创建,函数执行结束或者抛出异常时结束。

关于命名空间,读者还需要在日后的开发实践中慢慢体会,它会融会到你的编程过程中,有时候你是觉察不到的,正所谓“随风潜入夜,润物细无声”。


总目录   |   上节:zip()补充   |   下节:类(1)

如果你认为有必要打赏我,请通过支付宝:[email protected],不胜感激。

results matching ""

    No results matching ""