模块与包
章节
- 模块
- 包
- 组织包和模块
- __init__.py 与 __all__
一、模块
简单说模块就是包含了一堆代码的文件,在实际的项目开发中通常不会把所有代码写到一个模块文件中, 而是按照功能划分放到n个模块文件中,每个模块有不同的功能,可以相互调用,其主要作用是
- 把程序分解成模块,代码复用
- 有层次的组织命名空间,模块里的对象可以看成模块的属性,不同模块中相同名字的对象是不同的
模块可以分类为:
- 内建模块,比如os、sys
- 自定义模块
- 第三方模块,别人写好的模块,比如requests、flask等
1、自定义模块

上面这些以.py后缀的文件称之为模块
2、导入模块
导入模块可以通过 import 或 from 语句,有两种导入形式: 绝对导入和相对导入
绝对导入:
import xx from xx import xx from xx import xx, xx, xx from xx.xx import xx, xx from xx import xx as new_xx from xx import * # 不推荐
相对导入:
from . import xx from .xx import xx from ..xx import xx,xx from .. import xx
绝对与相对导入和linux里面的概念是一样的,以模块所在为当前目录基准,需要注意的是相对导入只能用from语句,主(入口) 模块不能使用相对导入
3、模块的搜索路径
先找该名字的内置模块,如果没有找到,则从sys.path列表中的目录依次搜索,搜索到了就不往后面搜索,如果搜索不到则抛出ImportError异常
>>> import sys >>> sys.path ['', '/usr/local/python/lib/python35.zip', '/usr/local/python/lib/python3.5', '/usr/local/python/lib/python3.5/plat-linux', '/usr/local/python/lib/python3.5/lib-dynload', '/usr/local/python/lib/python3.5/site-packages']
sys.path的组成
- 包含导入脚本的目录 ( 即当前目录)
PYTHONPATH变量 (其形式同shell 变量PATH的语法).- 安裝时定义的目录.
通常sys.path的第一个路径是当前的目录,所以可以添加自己的模块到sys.path,这样就可以被搜索到
sys.path.append('你的目录路径') 或 sys.path.insert(0, '你的目录路径')
4、sys.modules与import导入背后的操作
>>> import sys >>> sys.modules) // 输出: {'builtins': <module 'builtins' (built-in)>, 'sys': <module 'sys' (built-in)>, '_frozen_importlib': <module '_frozen_importlib' (frozen)>, '_imp': <module '_imp' (built-in)>, '_warnings': <module '_warnings' (built-in)>, '_thread': <module '_thread' (built-in)>, '_weakref': <module '_weakref' (built-in)>, '_frozen_importlib_external': <module '_frozen_importlib_external' (frozen)>, '_io': <module 'io' (built-in)>, 'marshal': <module 'marshal' (built-in)>, 'nt': <module 'nt' (built-in)>, 'winreg': <module 'winreg' (built-in)>, 'zipimport': <module 'zipimport' (built-in)>, 'encodings': <module 'encodings' from 'C:\\Program Files\\Python36\\lib\\encodings\\__init__.py'>, 'codecs': <module 'codecs' from 'C:\\Program Files\\Python36\\lib\\codecs.py'>, '_codecs': <module '_codecs' (built-in)>, 'encodings.aliases': <module 'encodings.aliases' from 'C:\\Program Files\\Python36\\lib\\encodings\\aliases.py'>, 'encodings.utf_8': <module 'encodings.utf_8' from 'C:\\Program Files\\Python36\\lib\\encodings\\utf_8.py'>, '_signal': <module '_signal' (built-in)>, '__main__': <module '__main__' from 'e:/NOTE/PYTHON/demo/n.py'>, 'encodings.latin_1': <module 'encodings.latin_1' from 'C:\\Program Files\\Python36\\lib\\encodings\\latin_1.py'>, 'io': <module 'io' from 'C:\\Program Files\\Python36\\lib\\io.py'>, 'abc': <module 'abc' from 'C:\\Program Files\\Python36\\lib\\abc.py'>, '_weakrefset': <module '_weakrefset' from 'C:\\Program Files\\Python36\\lib\\_weakrefset.py'>, 'site': <module 'site' from 'C:\\Program Files\\Python36\\lib\\site.py'>, 'os': <module 'os' from 'C:\\Program Files\\Python36\\lib\\os.py'>, 'errno': <module 'errno' (built-in)>, 'stat': <module 'stat' from 'C:\\Program Files\\Python36\\lib\\stat.py'>, '_stat': <module '_stat' (built-in)>, 'ntpath': <module 'ntpath' from 'C:\\Program Files\\Python36\\lib\\ntpath.py'>, 'genericpath': <module 'genericpath' from 'C:\\Program Files\\Python36\\lib\\genericpath.py'>, 'os.path': <module 'ntpath' from 'C:\\Program Files\\Python36\\lib\\ntpath.py'>, '_collections_abc': <module '_collections_abc' from 'C:\\Program Files\\Python36\\lib\\_collections_abc.py'>, '_sitebuiltins': <module '_sitebuiltins' from 'C:\\Program Files\\Python36\\lib\\_sitebuiltins.py'>, 'sysconfig': <module 'sysconfig' from 'C:\\Program Files\\Python36\\lib\\sysconfig.py'>}
>>> import sys >>> sys.modules.keys() dict_keys(['builtins', 'sys', '_frozen_importlib', '_imp', '_warnings', '_thread', '_weakref', '_frozen_importlib_external', '_io', 'marshal', 'nt', 'winreg', 'zipimport', 'encodings', 'codecs', '_codecs', 'encodings.aliases', 'encodings.utf_8', '_signal', '__main__', 'encodings.latin_1', 'io', 'abc', '_weakrefset', 'site', 'os', 'errno', 'stat', '_stat', 'ntpath', 'genericpath', 'os.path', '_collections_abc', '_sitebuiltins', 'sysconfig'])
import sys # 导入sys模块 print('m' in sys.modules.keys()) # False, 因为还没有导入m模块,所以m不在sys.modules.keys()中 import m # 导入m模块 print('m' in sys.modules.keys()) # True, 说明已经加入到sys.modules中,然后执行m模块里面的代码 import m # 再次导入m模块, 不会执行m模块里面的代码,因为m已经在sys.modules中了
- 先检查sys.modules这个字典有没有m这个模块名
- 如果有则不会去执行m模块中的代码
- 如果没有则执行m模块中的代码,并加入到sys.modules在内的中
- 然后起个同名的名字m 指向模块对象(当前的命名空间)
小结: 当import 模块时会去sys.modules字典中查找,如果没有则添加到sys.modules中并执行模块里面的代码,如果有说明已经导入了不会在执行模块里面的代码, 是一种优化。 就是已经导入的模块就不要在执行了模块代码了。为什么呢? 试想一下一个程序如果有包含十多次 import os,那么要执行十多次os里面的代码,效率肯定比较低的。
注: from 与 import的导入本质上也是一样的
# filename: b.py import c print('我是b模块啊') # filename: c.py import b print('我是c模块啊') print('hello c模块') # python c.py 我是c模块啊 hello c模块 我是b模块啊 我是c模块啊 hello c模块
6、循环导入的问题
这个例子, 在同级目录中有 b.py 和 c.py 两个模块,它们之间相互导入, 造成了循环(交叉)引用

用sys.modules和import 导入机制分析为什么会这样
- b.py 模块 导入 c.py 模块中的 c_method, c模块被执行了两次,第一次已经加入到sys.modules中,第二次则不会执行c模块,所以报错
- c.py 模块 导入 b.py 模块中的 b_method,b模块被执行了两次,第一次已经加入到sys.modules中,第二次则不会执行b模块,所以报错
如何解决? 循环引用的问题根本原因是模块划分以及设计不合理导致的, 应该将两个模块需要的功能提取出来放到另一个模块, 都从这个模块导入

7 .pyc文件
当第一次import 模块 时会产生一个后缀为.pyc文件,这个是python解释器编译好的模块文件,后面需要导入这个模块时它会优先导入这个.pyc文件,这样更快,如果有改动的话也会记录。这些都是python的优化
可以通过模块的__cached__属性查看编译好的.pyc文件,通常会放到当前__pycache__目录下
>>> import os >>> os.__cached__ '/usr/local/python/lib/python3.5/__pycache__/os.cpython-35.pyc'
要点:
- 不推荐 from xxx import * 这种导入方式, 因为可能污染当前的命名空间
- 自定义模块不要内置的模块名冲突, 因为在搜索模块的时候第一个路径当前目录
- 以下划线开头的对象不会被 from xxx import * 导入, 如a模块有_a = 3语句, 在b模块中即使 from a import * 也不会导入_a这对象
- 对于导入模块的顺序推荐先是内置模块、然后第三方模块、自定义模块
8、模块的属性
模块会包含一些特殊的属性,以双下划线开始和结尾
__file__ # 模块的绝对文件路径 __name__ # 模块的名称 __package__ # 模块的包名 __doc__ # 模块的文档字符串 __dict__ # 模块的命名空间字典,模块中所有的名字与对应的值 __builtins__ # 指向到内置的builtins模块 __cached__ # 模块被编译好的.pyc文件路径,通常在__pycache__目录下
#!/usr/bin/env python """ docstring for a module """
查看模块的特殊属性二、包
包是模块的集合,其实就是个目录,但目录里面需要有个特殊的__init__.py模块文件,它的作用就是把目录变成包或者在导入的时候进行一些初始化操作
可以把包看成是一种特殊的模块,但模块不是包

包也可以包括其它的子包,构成了顶级包 (父包) 与子包的关系,一般来说项目很大的时候可以分子包,像这样

三、组织模块和包
在实际的项目中怎么组织项目的包结构呢? 如你要写一个叫website的项目,基本的结构可以写成这样:
website/ # 项目的目录,你起个名字
├── bin # 放一些可执行的脚本, 类似于linux的bin目录下的二进制命令, 命名成scripts也行
│ ├── a.py
│ ├── b.py
│ └── c.py
├── docs # 存放项目相关的文档,比如图片、介绍之类的
│ ├── a.rst
│ ├── b.rst
│ └── images
│ ├── a.png
│ ├── b.png
│ ├── c.png
│ └── d.png
├── LICENS # 许可相关,可选,比如你做的是开源的项目
├── README.md # 介绍你的项目. 功能是什么么、怎么用之类的
├── requirements.txt # 项目的依赖外部python包
├── setup.py # 部署的脚本
├── tests # 放测试的代码, 应该把测试的代码单独放在其它地方.
│ ├── __init__.py
│ ├── test_a.py
│ ├── test_b.py
│ ├── test_c.py
│ └── test_d.py
└── website # 包,放你所有项目代码
├── a.py
├── b.py
├── c.py
├── d.py
└── __init__.py
如果你的项目很大,根据需求可以细分出很多的子包,参考下openstack的nove项目.

四、__init__.py与__all__
__init__.py 是一个特殊的模块文件,其主要的作用是把目录变成包或导入这个包的时候进行一些初始化的操作, 它可以为空,也可以有内容。当这个文件内容为空时,那么就只是把目录变成包。
使用的场景: 可以在里面写一些版本、作者等信息之类的,或者在里面导入其它模块或其它模块中的对象,这样当导入这个包的时候其实就相当帮忙 导入了很多模块
参考下 flask的__init__.py 中的内容,它就是把其它模块中的对象导入进来,然后你导入flask的时候也就有了这些对象.

所以这个__init__.py可以作为一种控制导入的方式,如果想导入模块的时候还想做一些其它的事情可以这样用.
__all__
如果在一个模块文件里面定义了__all__, 当用 from 模块 import * 时仅仅只会把__all__中所定义的对象导入到当前的命名空间.
使用场景: 当使用 from 模块 import * 会把模块中所有的对象都导入到当前的命名空间, 但假设你是模块的作者,有些功能还在开发并不想被这样全部发布出去,这时就可以把仅能够对外的功能放到__all__中,这样就不会被 全部导入
比如tests包有a、b、c三个模块

在__init__.py中指定 __all__
__all__ = ['a', 'b']
当 from test import * 时会导入a和b模块到当前的命名空间, 而不会导入c模块
总结:
- 模块与包是组织 对象的方式, 其主要的作用是代码重用、模块化、可维护性、扩展性.
- 一切都是对象, 模块也是对象,模块里面所有的对象都可以看成属性
- 自定义的模块名最好不要和内置模块名冲突
- 模块是全局的作用域
- 理解__init__.py与__all__的作用和用法
- 了解如何组织项目的模块或包、防止循环引用
参考:
http://docspy3zh.readthedocs.io/en/latest/tutorial/modules.html#tut-moremodules
https://pythonguidecn.readthedocs.io/zh/latest/writing/structure.html
浙公网安备 33010602011771号