11. 包和模块的奇淫巧技
- 构建简单的包
在项目代码中,如果需要形成分层模块构成的包
只需要在文件系统上分别定义不同的文件夹,并且每个目录都定义了 __init__.py文件
graphics/
__init__.py primitive/ __init__.py line.py fill.py text.py formats/ __init__.py png.py jpg.py |
这样就可以进行import了
__init__.py会在导入的时候被导入,建立这个目录,也就是命名空间
如果我们递归的进入到更深处,那么每一层的init.py都会被导入
一般来说我们不会在init.py中书写代码,特殊情况需要用其加载子模块
甚至有时候我们都不会定义init.py,让其成为一个命名空间包即可
- 控制可以被导入的内容
一般来说我们想要某个类或者方法不被导入,会在起名的时候在前面加上两道下划线
但如果我们想要控制 import.*的动作,则应该需要定义一个变量 __all__来进行控制,只有包含在all变量数组中的东西,才会被导入
__all__ = [‘spam’, ‘grok’]
- 相对路径导入
如果同在一个项目中,那么是可以使用相对路径导包的
比如我们有如下路径的项目
mypackage/
__init__.py A/ __init__.py spam.py grok.py B/ __init__.py bar.py |
如果我们在spam.py下要导入grok
那么可以是from . import grok
如果要导入 bar,那么可以使用 from ..B import bar
这样使用比绝对路径来说,是一种更轻量的软变化
但是需要注意,只能在包的目录内定义
- 不设置init的目录
如果一个目录不设置init的话,那么我们可以联合起来,作为一个命名空间使用
就比如下面的代码
foo-package/
spam/ blah.py bar-package/ spam/ grok.py |
这样我们有一个共同的spam空间
我们如果将上面的foo-package和bar-package联合导入的话
>>> sys.path.extend([‘foo-package’, ‘bar-package’])
>>> import spam.blah
>>> import spam.grok
那么我们可以直接导入spam下的blah和grok的
这样我们可以看下spam这个包的命名空间
>>> import spam
>>> spam.__path__
_NamespacePath([‘foo-package/spam’, ‘bar-package/spam’])
- 重新加载模块
我们可以使用imp.load来重新加载模块
>>> imp.reload(spam)
重新加载模块一般很有用,会擦除底层字典的内容来进行刷新,但是有些地方不会刷新,比如 from xxxx import xxx
这种就不会刷新
所以应该避免重新加载模块
- 运行压缩文件
如果我们希望运行一个项目,那么我们应该将自己的目录放在一个 __main__,py文件中
myapplication/
spam.py bar.py grok.py __main__.py |
然后直接运行即可
bash % python3 myapplication
如果我们将项目打包为zip文件,那么也可以直接使用
bash % python3 myapplication.zip
- 添加文件夹到sys.path
如果想要添加新的模块到sys.path下,那么我们有两种方式
一种方式如下
bash % env PYTHONPATH=/some/dir:/other/dir python3
或者是创建一个.pth文件,进行列举
# myapplication.pth
/some/dir
/other/dir
.pth文件放在python的site-packages目录
在运行的时候也可以通过sys.path.insert 添加
sys.path.insert(0, ‘/some/dir’)
这种方式非常不推荐,因为往往换环境就无法使用了,所以很不建议
- 通过字符串来导入包
我们可以使用importlib.import_module() 来进行手动导入
>>> math = importlib.import_module(‘math’)
>>> math.sin(2) |
- 安装第三方包
一般来说直接 pip install即可,但是可能存在着用户权限不足的原因
那么我们可以使用 -user来为某一个用户进行安装
pip install –user packagename
在sys.path中存在着用户的site-packages目录,其优先级由于site-packages目录
在里面安装的包比系统已经安装的包优先级高
- 创建虚拟环境
如果需要创建一个新的python环境,这个新环境安装的包和模块不会影响到系统级别的python环境
这时候可以使用pyenv来创建一个虚拟环境,会创建一个新的虚拟环境
bash % pyvenv Spam
这时候会创建一个Spam目录
进入到其中会存在新的编译器
bash % Spam/bin/python3
Python 3.3.0 (default, Oct 6 2012, 15:45:22) [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin |
这时候再进行安装的话,因为site-package是一个新的目录,所以不会影响系统环境
并且我们还是用的虚拟环境的解释器,双重保障
- 分发包
如果希望将自己的库分享出去
比如我们有一个如下的库
projectname/
README.txt Doc/ documentation.txt projectname/ __init__.py foo.py bar.py utils/ __init__.py spam.py grok.py examples/ helloworld.py … |
如果希望分享的话,那么我们可以编写一个setup.py
from distutils.core import setup
setup(name=’projectname’, version=’1.0′, author=’Your Name’, author_email=’you@youraddress.com’, url=’http://www.you.com/projectname’, packages=[‘projectname’, ‘projectname.utils’], ) |
以及一个MANIFEST.in文件,列出所有需要包含的非源码文件
# MANIFEST.in
include *.txt recursive-include examples * recursive-include Doc * |
然后将这两个文件放在包的最顶级,这样就可以进行源码分发包了
% bash python3 setup.py sdist
这样就会创建一个”projectname-1.0.zip” 或 “projectname-1.0.tar.gz”
需要注意的是,setup.py中需要列出所有包源码的子目录,一个常见的错误就是列出了所有包的顶级目录,忘了其包含的子组件
这里需要我们可以考虑使用setuptools,distribute等进行代替