2.19. 编写模块¶
任何 .py 文件都是一个模块。把一个不断增长的脚本拆分到几个文件中,能让每个文件保持简短,并让通用的辅助代码在脚本之间共享。
2.19.1. 拆分一个脚本¶
把一组相关的函数提取到它们自己的文件中:
camera_utils.py:
def banner():
print("OpenMV")
def label(text):
return "[" + text + "]"
main.py:
import camera_utils
camera_utils.banner()
print(camera_utils.label("ready"))
输出:
OpenMV
[ready]
这两个文件并排放在同一个目录里。当 main.py 运行时,import camera_utils 会读取一次 camera_utils.py,执行它的顶层语句,并把得到的模块对象绑定到 main 中的名称 camera_utils 上。从其他任何地方再来一次 import camera_utils 都会返回同一个对象 —— 模块在首次加载后会被缓存。
模块的名称来自它的文件名,所以 camera_utils.py 以 import camera_utils 的形式被导入。
2.19.2. 多文件模块(包)¶
模块也可以是一个文件的 目录,而不是单个 .py。该目录的名称成为模块名,里面的文件则是它的 子模块:
camera_utils/
__init__.py
text.py
timing.py
__init__.py 是包本身被导入时运行的文件;它可以是空的,也可以从子模块中重新导出选定的名称。子模块通过点号分隔的名称来访问:
import camera_utils.text
from camera_utils.timing import elapsed
camera_utils.text.label("ready")
在包内部,子模块之间既可以通过完整的点号名称相互访问,也可以通过以一个前导点表示“本包”的 相对导入 来访问:
camera_utils/timing.py:
from . import text # the sibling submodule
def stamp(value):
return text.label(str(value))
若要改为引入一个特定的名称,就在点号分隔的同级模块名后面写出它:
from .text import label
def stamp(value):
return label(str(value))
相对导入让一个包保持自包含:重命名包目录无需编辑每一个子模块。
当单个文件增长超过一个舒适的大小时,或者当一组相关模块应当归属于同一个命名空间下时,就使用包。对于日常脚本,单个 .py 文件就足够了。
2.19.3. __name__ 守卫¶
每个模块都有一个内置名称 __name__。它的值取决于文件是如何被使用的:
当文件被 直接运行 时,
__name__被设为字符串"__main__"。当文件被另一个脚本 导入 时,
__name__被设为模块名 —— 即不含.py的文件名。
利用这一点的惯用写法是:
label_util.py:
def label(text):
return "[" + text + "]"
if __name__ == "__main__":
print(label("self-test"))
输出(直接运行时):
[self-test]
当同一个文件改为被 导入 时,__name__ 被设为模块名,所以 if 块会被跳过,不会有额外的东西运行:
>>> import label_util
>>> label_util.__name__
'label_util'
用这个模式给一个库文件附加一个快速的冒烟测试或演示,而不会扰乱那些导入它的脚本。