一、把模块按层次结构组织成包
创建一个软件包在目录中定义一个__init__.py文件。
__init__.py文件的目的就是包含可选的初始化代码,当遇到软件包中不同层次的模块时会触发运行。
对于导入语句:>>> import graphics.formats.jpg
文件graphics/__init__.py 和 graphics/formats/__init__.py 都会在导入文件graphics/formats/jpg.py之前优先得到导入!
大部分情况下,把__init__.py文件留空也是可以的。但是,在某些特定的情况下__init__.py文件可用来加载子模块。
# graphics/formats/__init__.py from . import jpg from . import png
二、对所有符号的导入进行控制
使用>>> from module import * 语句时,在模块中定义__all__,那么只有显式列出的符号名才会被导出。
如果将__all__定义成一个空列表,那么任何符号都不会被导出。
如果__all__包含有未定义的名称,那么在执行import语句时会产生一个AttributeError异常。
__all__ = [ 'spam', 'grok' ]
三、用相对名称来导入包中的子模块
要在软件包的子模块中导入同一个包中其他的子模块,请使用相对名称来导入。
mypackage/ __init__.py A/ __init__.py spam.py grok.py B/ __init__.py bar.py
如果模块mypackage.A.spam要导入位于同一个目录中的模块grok,应该使用
>>> from . import grok
如果模块mypackage.A.spam要导入位于不同目录中的模块 B.bar,应该使用
>>> from ..B import bar
>>> from mypacksge.A import grok
使用绝对名称的缺点在于这么做会将最顶层的包名称硬编码到源代码中,这使得代码更加脆弱,如果想重新组织一下结构会比较困难。
▲ 一切包的相对导入都是在主程序所在目录之下进行的,不能导入它的上一级目录中的包。
▲ 位于脚本顶层的模块不能使用相对导入。
▲ 包的某个部分是直接以脚本的形式执行的,这种情况下也不能使用相对导入。
四、将模块分解成多个文件
可以通过将模块转换为包的方式将模块分解成多个单独的文件。
# mymodule.py class A: def spam(self): print('A.spam') class B(A): def bar(self): print('B.bar')
把mymodule.py分解为两个文件,每个文件包含一个类的定义。
mymodule/ __init__.py a.py b.py # a.py class A: def spam(self): print('A.spam') # b.py from .a import A class B(A): def bar(self): print('B.bar') # __init__.py from .a import A from .b import B
创建一个包目录,并通过__init__.py文件将各个部分粘合在一起。
引入惰性导入的概念。__init__.py文件一次性将所有需要的组件都导入进来。
如果只希望在实际需要的时候才加载那些组件。为了实现这个目的,下面对__init__.py文件做了修改:
# __init__.py def A(): from .a import A return A() def B(): from .b import B return B()
惰性加载的主要缺点在于会破坏继承和类型检查机制。
>>> if isinstance(x, mymodule.A): # Error
>>> if isinstance(x, mymodule.a.A): # OK
五、让各个目录下的代码在统一的命名空间下导入
使各个单独的目录统一在一个公共的命名空间下,可以把代码像普通的Python包那样进行组织。
但是对于打算合并在一起的组件,这些目录中的__init__.py文件则需要忽略。
foo-package/ spam/ blah.py bar-package/ spam/ grok.py
spam同来作为公共的命名空间。注意这两个目录中都没有出现__init__.py文件。
将foo-package和bar-package都添加到Python的模块查询路径中,然后尝试做一些导入操作。
>>> sys.path.extend(['foo-package', 'bar-package'])
>>> import spam.blah
>>> import spam.grok
两个不同的包目录合并在了一起,可以随意导入spam.blah或者spam.grok。
原理:
使用了命名空间包的特性。
命名空间包是一种特殊的包,用来合并不同目录下的代码,把他们放在统一的命名空间之下进行管理。
关键在于确保统一命名空间的顶层目录不包含__init__.py文件。
命名空间包的一个重要特性是任何人都可以用自己的代码来扩展命名空间中的内容。
每个包都在__file__模块中保存了全路径。
如果缺少__file__属性,这个包就是命名空间。
六、重新加载模块
重新加载一个已经加载过了的模块,使用imp.reload()
>>> import spam
>>> import imp
>>> imp.reload(spam)
但是,reload()操作不会更新 from module import xxx 这样的语句导入的定义。
七、让目录或zip文件称为可运行的脚本
创建一个目录或zip文件,在其中添加一个__main__.py,这是一种打包规模较大的Python应用程序的可行方法。
八、读取包中的数据文件
包的结构:
mypackage/ __init__.py somedata.dat spam.py
>>> import pkgutil
>>> data = pkgutil.get_data(__package__, 'somedata.dat')
get_data()的第一个参数是包含有包名的字符串。我们可以直接提供这个字符串,或者使用__package__这个特殊变量。
第二个参数是要获取的文件相对于包的名称。
九、添加目录到sys.path中
创建一个.pth文件,将目录写出来:
# myapplication.pth /some/dir /other/dir
这个.pth文件需要放在Python的其中一个site-packages目录中,一般来说位于/usr/local/lib/python3.3/site-packages。
在解释器启动的时候,只要.pth文件中列出的目录存在于文件系统中,那么它们就会被添加到sys.path中。
十、使用字符串中给定的名称来导入模块
当模块或包的名称以字符串的形式给出时,可以使用importlib.import_module()函数来手动导入这个模块。
>>> import importlib
>>> math = importlib.import_module('math')
>>> math.sin(2)
导入包 from . import b 的用法:
>>> b = importlib.import_module('.b', __package__)
十三、安装只为自己所用的包
Python有一个用户级的安装目录,通常位于~./local/lib/python3.3/site-packages这样的目录下。
python3 setup.py install -user 或者
pip install --user packagename
用户级的site-package目录通常会在sys.path中出现,而且位于系统级的site-package目录之前。
因此,采用这种技术安装的包比已经安装到系统中的包优先级要高
十四、创建新的Python环境
通过pyvenv命令创建一个新的虚拟环境。
bash % pyvenv Spam
Spam目录下会创建出 bin、include、lib、pyvene.cfg目录,并且在bin目录下有一个可使用的Python解释器。
bash % Spam/bin/python3
创建虚拟环境大部分原因都是为了安装和管理第三方的包。
下一步通常是安装一个包管理器,distribute或者pip
如果想将已经安装过的包引入,使其作为虚拟环境的一部分,那么可以使用--system-site-packages来创建虚拟环境。
bash % pyvenv --system-site-packages Spam
十五、发布自定义的包
把编写的库分发给其他人使用。
典型的程序库结构:
projectname/ README.txt Doc/ documentation.txt projectname/ __init__.py foo.py bar.py utils/ __init__.py spam.py grok.py examples/ hellworld.py
首先编写一个setup.py文件。
from distutils.core import setup setup(name='projectname', varsion='1.0', author='Your Name', author_email='you@youaddress.com', url='http://www.you.com/projectname', packages=['projectname', 'projectname.utils'] )
需要手动列出包中的每一个子目录:packages=['projectname', 'projectname.utils']
接下来创建一个MANIFEST.in文件。列出各种希望包含在包中的非源代码文件:
# MANIFEST.in include *.txt recursive-include examples * recursive-include Doc *
确保setup.py和MANIFEST.in文件位于包的顶层目录。
% bash python3 setup.py sdist
系统会创建出projectname-1.0.zip或者projectname-1.0.tar.gz这样的文件。