第八章: 模块, 包 与 分发
描述:
大型Python程序以模块和包的形式组织。另外,Python标准库中包含大量模块。本章详细介绍模块和包系统。还将提供有关如何安装第三方模块和分发源代码的信息。
8.1模块与import语句
任何Python源文件都能以模块的形式使用。例如,考虑以下代码:
# spam.py a = 37 def foo (): print("I‘m foo and a is Is" % a) def bar(): print("I‘m bar and I‘m calling foo") foo() class Spam(object): def grok(self): print ("I‘m Spam. grok")
要以模块的形式加载这段代码,可以使用import spam语句。
首次使用import加载模块时,它将做3件事:
(1) 创建新的命名空间,用作在相应源文件中定义的所有对象的容器。在模块中定义的函数和方法在使用global语句时将访问该命名空间。
(2) 在新创建的命名空间中执行模块中包含的代码。
(3) 在调用函数中创建名称来引用模块命名空间。这个名称与模块的名称相匹配,按如下方式使用:
需要强调的是,import 执行已加载源的文件中的所有语句。如果模块执行计算或生成了除定义变量、函数和类以外的输出,就可以看到生成的这些结果。
而且,一个容易混淆的模块问题就是对类的访问。请记住,如果文件 spam.py 定义了类Spam,必须使用名称spam.Spam来引用该类。
要导入多个模块,可以为import提供逗号分隔的模块名称列表
ex:
import socket, os, re
限定符
用于引用模块的名称可以使用as限定符进行更改,例如:
import spam as sp import socket as net sp.foo() sp.bar() net.gethostname()
使用其他的名称加载模块时,新名称仅应用于出现了import语句的源文件或上下文。其他程序模块仍然可以使用模块原来的名称加载它。
更改已导入模块的名称对于编写可扩展的代码很有用。
例如,设想有两个模块xmlreader.py和 csvreader.py,它们都定义了函数read_data(filename),作用是从文件中读取一些数据,但采用不同的输入格式,可以编写代码来选择性地挑选读取模块,
例如:
if format == ‘xml‘: import xmlreader as reader elif format == ‘csv‘: import csvreader as reader data = reader.read_data(filename)
模块是Python中的第一类对象。
也就是说它们可以分配给变量,放置在列表等数据结构中,以及以数据的形式在程序中传递。
例如,上一个例子中的reader变量只用于引用相应的模块对象。
在后台, 模块对象是位于字典之上的一层,这个字典用于持有模块命名空间的内容。
这个字典以模块的形式使用,并且只要査找或更改模块中的值,就会对该字典进行相应操作。
import语句可以出现在程序中的任何位置。
但是,每个模块中的代码仅加载和执行一次,无论 import语句被使用了多少次。后续的import语句仅将模块名称绑定到前一次导入所创建的模块对象。
你可以找到一个字典,其中包含变量sys.modules中所有当前加载的模块。该字典将模块名称映射到模块对象。该字典的内容用于确定import是否加载模块的全新拷贝。
8.2从模块导入选定符号
from语句用于将模块中的具体定义加载到当前命名空间中。
from语句相当于import,但它不会创建一个名称来引用新创建的模块命名空间,而是将对模块中定义的一个或多个对象的引用放到当前命名空间中:
from spam import foo #导入spam并将foo放在当前命名空间中 foo() # 调用spam.foo() spam.foo() # NameError: spam
from语句还会接受用逗号分隔的对象名称列表。
例如:
1 |
|
如果要导入一个极长的名称列表,可以将名称放在括号中。这样可以轻松地将import语句放在多行上,
例如:
1 2 3 |
|
另外, as限定符可用于重命名使用from导入的具体对象,
例如:
1 2 |
|
星号(*)通配符也可用于加载模块中的所有定义,但以下划线开头的定义除外,例如:
将所有定义加载到当前命名空间中
from module import * 语句只能在模块最顶层使用。
具体来讲,在函数体内使用这种导入形式是不合法的,原因在于这种导入语句与函数作用域规则之间具有独特的交互方式。
例如,将函数编译为内部字节码时,函数中使用的所有符号都需要完全指定。
通过定义列表__all__, 模块可以精确控制from module import *导人的名称集合,例如:
#模块: spam.py
1 |
|
使用from导入形式导入定义不会更改定义的作用域规则。
例如,考虑以下代码:
1 2 3 |
|
在这个例子中,spam.py中foo()定义指的是全局变量a。将foo的引用放在不同命名空间中时, 不会更改该函数中的变量绑定规则。
因此,函数的全局命名空间始终是定义该函数的模块,而不是将函数导入并调用该函数的命名空间。这也适用于函数调用。
例如,在以下代码中,对bar()的调用会导致调用spam.foo(),而不是上一个代码示例中重新定义的foo():
1 2 3 4 5 |
|
对于from导入形式,另一个容易混淆的地方是全局变量的行为。例如,考虑以下代码
1 2 3 4 |
|
这里需要理解的一点是,Python中的变量赋值不是一种存储操作。
也就是说,上例中对a的赋值不会将新值存储在a中并覆盖以前的值。
而是将创建包含值42的新对象,并用名称a来引用它。此时, a不再绑定到导人模块中的值,而是绑定到其他对象。
由于此行为,不可能采用类似于全局变量或C或Fortran中的通用块的方式来使用from语句。
如果需要在程序中使用可变的全局程序参数,
可以将这些参数放在模块中并通过import语句显式使用模块名(也就是显式使用spam.a)。
8.3 以主程序方式执行
Python源文件可以通过两种方式执行。
import语句在自己的命名空间中以库模块的形式执行代码。但是,代码也可以主程序或脚本的形式执行。这适用于以脚本名称的形式为解释器提供程序时
% python spam.py
每个模块定义一个包含模块名称的变量__name__.
程序可以检査该变量,以确定它们在哪个模块中执行。解释器的顶级模块名为__main__。在命令行中指定或直接输入的程序将在__main__模块中运行。
有时,程序可能改变其行为,这取决于程序是以模块的形式导入还是在中运行。
例如:
模块可能包含一些测试代码,如果模块以主程序的形式执行,将执行这些测试代码,
如果模块只是由另一个模块导入,则不会执行测试代码。可以通过以下方式实现这一功能:
1 2 3 4 5 6 7 8 9 |
|
将源文件用作库的常见做法是,使用该技术来包含可选的测试或示例代码。例如,如果开发一个模块,可以将测试库功能的代码放在如上所示的if语句中,并以主程序的形式运行模块。导入此库的用户将无法使用该代码。
8.4 模块搜索路径
加载模块时,解释器在sys.path路径中搜索字典列表。
sys.path中的第一个条目通常是一个空字符串" ",表示当前正在使用的字典。
sys.path中的其他条目可能包含字典名称、.zip归档文件 和 .egg文件。
各个条目在sys.path中列出的顺序决定了加载模块时的搜索顺序。
要将新条目添加到 搜索路径中,只需将它们添加到该列表中即可。
尽管该路径通常包含字典名称,也可以将包含Python模块的zip归档文件添加到搜索路径中。通过这种方式,可以方便地将一组模块打包为一个文件。
例如:
设想创建两个模块foo.py和bar.py,并 将它们放在一个名为mymodules. zip的zip文件中。就可以按如下方式将这个文件添加到Python搜索路
径中
1 2 3 4 5 |
|
也可以使用zip文件目录结构中的具体位置。另外, zip文件可以与常规路径名称组件混合使用,
例如:
sys.path.append("/tmp/modules.zip/lib/python")
除了 .zip文件,还可以在搜索路径中添加 .egg文件。
.egg文件是由setuptools库创建的包。这是安装第三方Python库和扩展时使用的一种常见格式。.egg文件实际上只是添加了额外的元数据(如 版本号、依赖项等)的 .zip文件。因此,可以使用处理.zip文件的标准工具来从 .egg文件中检査和提取数据。
尽管支持 zip文件导入,但有一些限制需要注意。首先,只能从归档文件中导入 .py, .pyw, .pyc 和 .pyo文件.
使用c编写的共享库和扩展模块无法直接从归档文件中加载,但是setuptools等打包系统有时能够提供一种规避办法(通常将C扩展提取到一个临时目录并从该目录加载模块)。而且,从归档文件加载 .py文件时,Python不会创建 .pyc和 .pyo文件(稍后将介绍)。因此,一定要确保提前创建了这些文件,并将其放在归档文件中,以避免在加载模块时性能下降。
8.5 模块加载和编译
到目前为止,本章将模块描述为包含纯Python代码的文件。但是,使用import加载的模块实际上可分为4个通用类别:
- 使用Python编写的代码(.py文件)
- 已被编译为共享库或DLL的C或C++扩展
- 包含一组模块的包
- 使用C编写并链接到Python解释器的内置模块
査找模块(如fQo)时,解释器在sys.Path下的每个目录中搜索以下文件(按搜索顺序列出):
(1) 目录foo, 它定义了一个包
(2) foo.pyd、foo.so、foomodule.so或foomodule.dll (已编译的扩展)
(3) foo.pyo (只适用于使用了-o或-oo选项时)
(4) foo.pyc
(5) foo,py (在Windows上,Python还会査找.pyw文件。)
稍后将介绍包,已编译的扩展将在第26章介绍。对于.py文件,首次导入模块时,它会被编译为字节码并作为.pyc文件写回磁盘。在后续的导入操作中,解释器将加载这段预编译的字节码,除非.py文件的修改日期要更新一些(在这种情况下, 将重新生成.pyc文件)。.pyc文件与解释器的-o选项结合使用。这些文件包含已删除了行号、断言和其他调试信息的字节码。因此,这些文件会相对更小, 解释器的运行速度也会稍快一些。如果指定了-oo选项,而不是-o,那么还会从文件中删除文档字符 串。文档字符串只会在创建.pyo文件时删除,而不是在加载它们的时候。如果sys.path下的所有目录中都不存在这雙文件,解释器将检査该名称是否为内置的模块名称。如果不存在匹配的名称,将引
发 importError 异常
只有使用import语句才能将文件自动编译为 .puc 和 .pyo文件。在命令行或标准输入中指定的程序不会生成这类文件。另外,如果包含模块的 .py文件的目录不允许写入(可能是由于杈限不够或者该文件包含在一个zip归档文件中), 将不会创建这些文件。解释器的-B选项也可以禁止生成这些文件。
如果存在.pyc和.pyo文件,则可以没有相应的.py文件。因此,如果在打包代码时不希望包含源文件,可以只打包一组 .pyc 文件。但是请注意,Python提供了对内省(introspection) 和分解的广泛支 持。即使没有提供源文件,细心的用户仍然可以检査并找到程序的大量细节,还请注意,.pyc文件特定于具体的Python版本。因此,为某个Python版本生成的 .pyc 文件可能不适用于其他的Python版本。 import语句搜索文件时,文件名匹配是区分大小写的,即使机器上的底层文件系统不区分大小写也是如此,如Windows或OS X (但这些系统会保留名称的大小写形式)。所以,import foo将只导入文件foo.py,不会导入文件F00.PY,但是,作为一般规则,应该避免使用仅大小写形式不同的模块名称
8.6 模块重新加载和卸载
Python实际上不支持重新加载或卸载以前导入的模块。尽管可以从sys.modules删除模块,但这种方法通常不会从内存中卸载模块。这是因为,对模块对象的引用可能仍然存在于使用import加载该 模块的其他程序组件中。而且,如果在模块中定义了多个类实例,这些实例将包含对其类对象的引用, 类对象进而会拥有对定义它的模块的引用。
由于模块引用存在于多个位置,因此在更改了模块实现之后再重新加载该模块通常行不通。例如, 如果从sys .modules删除一个模块,然后使用import重新加载它,不会追溯性地更改程序中以前对该模块的引用。相反,你将拥有由最新的import语句创建的对新模块的引用,以及由其他部分代码中的 import 语句创建的一组对旧模块的引用。 这通常不是我们所期望的, 而且在正常的生产代码中使用这种导入方式也不安全,除非你能谨慎控制整个执行环境。
早期的Python版本提供了reload()函数来重新加载模块。但是,使用该函数也不是真正安全的
(原因同上),而且通常不鼓励使用它,除非用作调试辅助措施。Python3完全删除了这一功能。所以, 最好不要使用它。
最后应该注意的是,Python的C/C++ 扩展无法以任何方式安全地卸载或重新加载。Python没有提供对此操作的任何支持,而且底层操作系统也可能会禁止这么做。因此,唯一的解决办法是重新启动 Python解释器进程。
8.7 包
包可用于将一组模块分组到一个常见的包名称下。这项技术有助于解决不同应用程序中使用的模块名称之间的命名空间冲突问题。包是通过使用与其相同的名称创建目录,并在该目录中创建文件 __init___.py来创建的。然后,如果需要,可以向该目录中放入其他源文件、编译后的扩展和子包。
例如,可以按如下形式组织一个包:
Graphics/
__ init __ .py
Primitive/
__ init__ .py
lines.py
fill.py
text.py
...
Graph2d/
__init__.py
plot2d.py
...
Graph3d/
__init__.py
plot3d.py
...
Formats/
__init__.py
gif.py
png.py
tiff.py
jpeg.py
import语句用于通过多种方式从包中加载模块:
□ import Graphics.Primitive.fill
这条语句加载子模块Graphics.Primitive.fill。该模块的内容必须显式命名,如Graphics.Primitive.fill.floodfill(img,x,y,color).
□ from Graphics.Primitive import fill
这条语句加载子模块fill,支持以不带包前缀的形式使用它,如fill.floodfillUmg,
x,y,color)
□ from Graphics.Primitive.fill import floodfill
这条语句加载子模块fill,但支持直接访问floodfill函数,如floodfill (img,x, y, color)。
只要第一次导入包中的任何部分,就会执行文件 __init__.py 中的代码. 这个文件可以为空,但也可以包含可执行特定于该包的初始化工作的代码。在import语句执行期间,遇到的所有 __init__.py文件都会执行。因此,前面显示的import Graphics.Primitive.fill 语句将会首先执行Graphics目录中的__init__.py文件,然后执行Primitive目录中的__init__.py文件。
在使用包时,处理下面这条语句时需要小心:
from Graphics.Primitive import *
使用该语句的程序员通常希望将与某个包相关联的所有子模块导入到当前命名空间中。但是,由于各个系统之间的文件名约定不同(特别是在区分大小写上),Python无法准确地确定各个模块的具体 内容。结果,该语句只会导人在Primitive目录的 __init__.py 文件中定义的所有名称。这种行为可以通过定义列表__all__来修改,该列表包含与该包相关的所有模块名称。这个列表应该在包 __init__.py文件中定义,例如:
# Graphics/Primitive/__init__.py
__all__ = ["lines","text","fill"]
现在,用户发出 from Graphics.Primitive import * 语句时,将按预期加载所列出的所有子模块。
使用包时需要注意的另一个问题出现在子模块想要导入同一个包中的其他子模块时。例如,假设 Graphics.Primitive.fill 模块想要导入Graphics.Primitive.lines模块。为此,只需使用完全限定名称(如from Graphics.Primitives import lines)或使用包的相对导入,例如:
# fill.py
from . import lines
在这个例子中,语句from.import lines中使用的 . 表示与调用模块相同的目录(同级目录)。因此,该语句会在与文件 fill.py 相同的目录中寻找模块 lines 。特别需要注意的一点是,要避免使用import 样的语句来导入包的子模块。在早期的Python版本中,无法确定import module 语句引用的是标准库模块还是包的子模块。早期的Python版本将首先尝试从与包含import语句的子模块相同的包目录加载模块,然后如果没有找到匹配项,则会转移到标准库模块。但是在Python3中,import 会假定一条绝对路径,只会从标准库加载相对导入能够更清楚地表明导入意图。
相对导入也可用于加载同一个包的不同目录中包含的子模块。例如,如果模块 Graphics.Graph2D.plot2(想要导入Graphics.Primitives.lines,它可以使用如下语句:
# plot2d.py
from ..Primitives import lines
其中的 .. 用于移出一个目录级别,Primitives指定一个不同的包目录。
相对导入只能使用from module import symbol形式的导入语句来指定。因此, import . . Primitives.lines 或 import .lines 这样的语句在语法上是不对的。而且,symbol 还必须是一个有效的标识符。最后,相对导入只能在一个包中使用,使用相对导入来引用位于文件系统中 不同目录内的模块是不合法的。
单独导入包名称不会导入包中所包含的所有子模块。例如,以下代码不会生效:
import Graphics
Graphics. Primitive, fill.floodfill (img,x,y, color) # 失敗!
但是,由于import Graphics 语句会扶行 Graphics 目录中的 __init__.py 文件,所以相对导入可用于自动加载所有子模块’
例如:
# Graphics/__init__.py
from . import Primitive, Graph2d, Graph3d
# Graphics/Primitive/__init__.py
from . import lines, fill, text, ...
现在import Graphics语句会导入所有子模块,并支持通过完全限定名称来使用它们。再次强调一下,包相对导入只能按如上所示的形式使用。如果使用import module样的简单语句,加载的会是标准库模块。
最后,Python导入一个包时,它将定义特殊变量 __path__ , 该变量包含一个目录列表,査找包的子模块时将捜索这个列表 ( __path__是 sys.path 变量特定于具体包的版本). __path__可通过 __init__文件中包含的代码访问,最初包含的一项具有包的目录名称。如果有必要,包可以向__path__列表提供更多目录,更改査找子模块时使用的搜索路径。
如果文件系统上某个包的组织结 构很复杂,并且不能顺利地匹配包的层次结构,那么这个变量将很有用。
8.8 分发Python程序和库
要将Python程序分发给他人,应该使用distutils模块。作为准备,应该首先将工作有序地组织到一个包含README文件、支持文档和源代码的目录中。通常,这个目录将包含一组库模块、包和脚 本。模块和包指的是将用import语句加载的源文件。脚本是将作为主程序由解释器运行的程序(例如, 作为python scriptname运行)。下面的例子给出了一个包含Python代码的目录:
spam/
README.txt
Documentation.txt
libspam.py # 一个库模块
spampkg/ # 一个支持模块包
__init__.py
foo.py
bar.py
runspam.py #将作为python runspam.py运行的脚本
应该有序地组织代码,在顶级目录中运行Python解释器时它也能够正常工作。例如,如果在 spam 目录中启动 Python, 应该能够导入模块、导入包组件以及运行脚本,而无需更改Python的任何设置, 如模块捜索路径。
组织好代码之后,在最顶级目录中(如上例中的spam)创建文件 setup.py。在该文件中添加以下代码:
# setup.py
from distutils.core import setup
setup(name = "spam",
version = -1.0",
py_modules = [‘libspam‘],
packages = [‘spampkg‘ ],
scripts = [‘runspam.py‘],
在setup()调用中, py_modules 参数是所有单一文件Python模块的列表,packages是所有包目录的列表,packages是所有包目录的列表, scripts是脚本文件的列表。如果你的软件没有任何匹配的组件(也就是说没有脚本),所有这些参数都可以省略。name是包的名称,version是字符串形式的版本号。
对setup() 的调用支持很多其他参数,这些参数提供了包的各种元数据。表8-1列出了可以指定的最常见参数。所有值都是字符串,除了classifiers参数,它是一个字符串列表,
如[‘Development Status :: 4 - Beta‘, ‘Programming Language :: Python‘],完整的列表可以在http://pypi.python.org 上找到。
表8-1 setup ()的参数
参数 | 描述 |
---|---|
name | 包的名称(必需) |
Version | 版本号(必需) |
author | 作者名称 |
author_email | 作者的电子邮件地址 |
maintainer | 维护者的名称 |
maintainer_email | 维护者的电子邮件地址 |
url | 包的主页 |
description | 包的简短描述 |
long_description | 包的详细描述 |
download_url | 包的下载位置 |
Classifiers | 字符串分类器列表 |
只需创建一个setup.py文件就足以进行软件源代码的分发了。
输入以下shell命令即可进行源代码分发:
% python setup.py sdiat
...
%
这将在目录spam/dist下创建一个归档文件,如spam-l.0.tar.gz 或 spam-1.0.zip。这就是将提供给其他人安装软件的文件。要安装软件,用户只需解压该归档文件并执行以下步骤:
% unzip spam-1.0.zip
...
% cd spam-1.0
% python setup.py install
...
这将把软件安装在本地Python分发目录下,使其可用作一般用途。模块和包通常安装在Python库 中名为site-packages的目录中。要找到该目录的准确位置,可以检査sys.path的值。在基于UNIX的系统上,脚本通常安装在与Python解释器相同的目录下,在Windows上,通常安装在Scripts目录下(对于典型安装,完整目录为C:\Python26\Scripts)。
在UNIX上,如果脚本第一行以 #! 开头并且包含文本python,安装程序将重写该行以指向本地 Python 安装。因此,如果编写的脚本已被硬编码为特定的 Python 位置,如/usr/local/bin/python,当安装 在Python位于不同位置的其他系统上时,这些脚本仍然能够运行。
setup.py文件还包含其他一些与软件分发有关的命令。如果输入python setup.py bdist, 将创建一个二进制分发程序,其中的所有.py文件都已预编译为.pyc文件并放置在与本地平台的目录结
构类似的目录结构中。只有当应用程序各部分具有平台依赖关系时(例如,可能还有一个需要编译的C扩展),才需要这种分发形式。如果在Windows机器上运行python setup.py bdist_wininst,将 创建一个.exe文件。打开该文件时,将启动Windows安装程序对话框,提示用户提供关于软件安装路径的信息。这种分发形式还会添加注册表项,以便以后可以方便地卸载包。
distutils 模块假定用户机器上已经拥有Python安装程序(已单独下载)。尽管也可以创建软件包来将 Python 运行时和软件绑定到单一二进制可执行文件中的软件包,但这不属于本书介绍的范围(请査看Py2exe或py2app等第三方模块,了解更多细节)。如果只需要将库或简单脚本分发给他人,通常无需使用Python解释器和运行时来打包代码。
最后应该注意,除了这里介绍的选项以外,distutils还有许多其他选项。第26章将介绍如何使用distutils编译C和C++扩展。
尽管不是标准Python分发程序的一部分,但Python软件仍然可用 .egg 文件的形式分发。这种格式可以使用流行的setuptools扩展创建(http://pypi.python.org/pypi/setuptools)。要支持setuptools, 只需按如下方式更改setup. py文件的第一部分:
# setup.py
try:
from setuptools import setup
except ImportError:
from distutils.core import setup
setup(name = "spam",
...
)
8.9安装第三方库
要査找第三方库和Python扩展,一个权威资源是位于http://pypi.python.org上的Python包索引 (Python Package Index, PyPI). 安装第三方模块通常很简单,伹是对于还要依赖于其他第三方模块的超大型包而言,可能会变得很复杂,对于更主要的扩展,通常可以找到特定平台的本机安装程序,这种安装程序使用一系列对话框来帮助你执行安装过程。对于其他模块,通常需要解压下载文件,査找 setup.py文件,然后键人python setup.py install来安装软件。
默认情况下,第三方模块安装在Python标准库的 site-packages 目录下。要访问该目录,通常需要根用户或管理员权限,如果不符合此条件,可以键入 python setup.py install --user 将模块安 装在用户的特定库目录中。这会将包安装在用户的特定目录下,如在UNIX上是/Users/beazley /.local/lib/python2.6/site-pack-ages
如果希望将软件安装在一个完全不同的地方,可以对 setup.py 命令使用 --prefix选项。例如, 键入 python setup.py install --prefix=/home/beazley/pypackages 会将模块安装在目录 /home/beazley/pypackages下。安装在非标准位置时,必须调整 sys.path 的设置, 才能使 Python 找到最新安装的模块。
请注意,Python的很多扩展都包含C或C++代码。如果已下载了源分发版,系统必须安装C++编译器才能运行安装程序。在UNIX、Linux或OS X 上,通常不会出现这样的问题,在Windows上,通 常必须安装某个版本的Microsoft Visual Studio。如果使用的是Windows平台,最好找到扩展的预編译版本
如果已安装了setuptools,则可以使用脚本 easy_install 安装包。只需键人 easy_install pkgname 即可安装特定的包。如果配置正确,键人该命令后将从PyPI下载合适的软件和相关的依赖项, 并进行安装。当然,你也可以自己控制安装过程。
如果希望将自己的软件添加到PyPI, 只需键入python setup.py register 这会将最新版本软件的元数据上传到该索引(请注意,必须首先注册用户名和密码)。
原文地址:https://www.cnblogs.com/amou/p/9032318.html