模块的显要特性:属性
导入和重载提供了一种自然的程序启动的选择,因为导入操作将会在最后一步执行文件。从更宏观的角度来看,模块扮演了一个工具库的角色,这将在第五部分学到。从一般意义上来说,模块往往就是变量名的封装,被认作是命名空间。在一个包中的变量名就是所谓的属性:也就是说,属性就是绑定在特定的对象上的变量名(就像一个
模块)。
在典型的应用中,导入者得到了模块文件中在顶层所定义的所有变量名。这些变量名通常被赋值给通过模块函数、类、变量以及其他被导出的工具。这些往往都会在其他文件或程序中使用。表面上来看,一个模块文件的变量名可以通过两个Python语句读取—— import和from,以及reload调用。
为了讲清楚,请使用文本编辑器创建一个名为myfile.py的单行的Python模块文件,其内容如下所示:
- title = "The Meaning of Life"
这也许是世界上最简单的Python模块文件之一了(它只包含了一行赋值语句),但是它已经足够讲明白基本的要点。当文件导入时,它的代码运行并生成了模块的属性。这个赋值语句创建了一个名为title的模块的属性。
可以通过两种不同的办法从其他组件获得这个模块的title属性。第一种,你可以通过使用一个import语句将模块作为一个整体载入,并使用模块名后跟一个属性名来获取它:
- % python# Start Python
- >>> import myfile# Run file; load module as a whole
- >>> print(myfile.title)# Use its attribute names: '.' to qualify
- The Meaning of Life
一般来说,这里的点号表达式代表了object.attribute的语法,可以从任何的object中取出其任意的属性,并且这是Python代码中的一个常用操作。在这里,我们已经使用了它去获取在模块myfile中的一个字符串变量title,即myfile.title。
作为替代方案,可以通过这样的语句从模块文件中获得(实际上是复制)变量名:
- % python# Start Python
- >>> from myfile import title# Run file; copy its names
- >>> print(title)# Use name directly: no need to qualify
- The Meaning of Life
就像今后看到的更多细节一样,from和import很相似,只不过增加了对载入组件的变量名的额外的赋值。从技术上讲,from复制了模块的属性,以便属性能够成为接收者的直接变量。因此,能够直接以title(一个变量)引用导入字符串而不是myfile.title(一个属性引用注3。
无论使用的是import还是from去执行导入操作,模块文件myfile.py的语句都会执行,并且导入的组件(对应这里是交互提示模式)在顶层文件中得到了变量名的读取权。也许在这个简单的例子中只有一个变量名(变量title被赋值给一个字符串),但是如果开始在模块中定义对象,例如,函数和类时,这个概念将会很有用。这样一些对象就变成了可重用的组件,可以通过变量名被一个或多个客户端模块读取。
在实际应用中,模块文件往往定义了一个以上的可被外部文件使用的变量名。下面这个例子中定义了三个变量名:
- a = 'dead'# Define three attributes
- b = 'parrot'# Exported to other files
- c = 'sketch'
- print(a, b, c)# Also used in this file
文件treenames.py,给三个变量赋值,并对外部世界生成了三个属性。这个文件并且在一个print语句中使用它自有的三个变量,就像在将其作为顶层文件运行时看到的结果一样:
- % python threenames.py
- dead parrot sketch
所有的这个文件的代码运行起来就和第一次从其他地方导入(无论是通过import或者from)后一样。这个文件的客户端通过import得到了具有属性的模块,而客户端使用from时,则会获得文件变量名的复本。
- % python
- >>> import threenames# Grab the whole module
- dead parrot sketch
- >>>
- >>> threenames.b, threenames.c
- ('parrot', 'sketch')
- >>>
- >>> from threenames import a, b, c# Copy multiple names
- >>> b, c
- ('parrot', 'sketch')
这里的结果打印在括号中,因为它们实际上是元组(本书的下一部分介绍的一种对象);目前我们可以暂时忽略它们。
一旦你开始就像这里一样在模块文件编写多个变量名,内置的dir函数开始发挥作用了。你可以使用它来获得模块内部的可用的变量名的列表。下面代码返回了一个Python字符串列表(我们将从下一章开始学习列表):
- >>> dir(threenames)
- ['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'a', 'b', 'c']
我们在Python 3.0和Python 2.6中分别运行它,较早的Python可能返回较少的名字。当dir函数就像这个例子一样,通过把导入模块的名称传至括号里,进行调用后,它将返回这个模块内部的所有属性。其中返回的一些变量名是“免费”获得的:一些以双下划线开头并结尾的变量名,这些通常都是由Python预定义的内置变量名,对于解释器来说有特定的意义。那些通过代码赋值而定义的变量(a、b和c)在dir结果的最后显示。
模块和命名空间
模块导入是一种运行代码文件的方法,但是就像稍后我们即将在本书中讨论的那样,模块同样是Python程序最大的程序结构。
一般来说,Python程序往往由多个模块文件构成,通过import语句连接在一起。每个模块文件是一个独立完备的变量包,即一个命名空间。一个模块文件不能看到其他文件定义的变量名,除非它显式地导入了那个文件,所以模块文件在代码文件中起到了最小化命名冲突的作用。因为每个文件都是一个独立完备的命名空间,即使在它们拼写相同的情况下,一个文件中的变量名是不会与另一个文件中的变量冲突的。
实际上,就像你将看到的那样,正是由于模块将变量封装为不同部分,Python具有了能够避免命名冲突的优点。我们将会在本书后面章节讨论模块和其他的命名空间结构(包括类和函数的作用域)。就目前而言,模块是一个不需要重复输入而可以反复运行代码的方法。
注意:import VS from:我应该指出,from语句在某种意义上战胜了模块的名称空间分隔的目的,因为from把变量从一个文件复制到另一个文件,这可能导致在导入的文件中相同名称的变量被覆盖(并且,如果发生这种情况的话,不会为你给出警告)。这根本上会导致名称空间重叠到一起,至少在复制的变量上会重叠。
因此,有些人建议使用import而不是from。然而,我不建议这么做,不仅因为from更短,而且因为它传说中的问题在实际中几乎不是问题。此外,这是由你来控制的问题,可以在from中列出想要的变量;只要你理解它们将是要赋的值,这不会比编写赋值语句更危险,而赋值是你可能想要使用的另一功能。