从单个文件到装满模块的文件夹
项目变大后,单个文件就不够用了。包(package)是 Python 可作为一个整体导入的模块文件夹。在文件夹里放一个名为 __init__.py 的文件(可以是空的),其中每个 .py 文件就成为该包的模块,用点号表示:quant/pricing.py 是 quant.pricing,quant/data/loaders.py 是 quant.data.loaders。包里面的文件夹是子包,各自需要 __init__.py。
# A small project on disk
#
# quant-tools/
# pyproject.toml
# README.md
# quant/
# __init__.py
# pricing.py
# data/
# __init__.py
# loaders.py
# tests/
# test_pricing.py
# quant/pricing.py
def mid(bid, ask):
return (bid + ask) / 2
# anywhere else in the project
from quant.pricing import mid
import quant.data.loaders
print(mid(184.0, 185.0)) # 184.5可扩展的目录结构
把可导入的代码放在以项目命名的一个包文件夹里,其他内容放在它旁边而不是里面:测试放在 tests/,用 README.md 说明项目,用 pyproject.toml 给项目命名并列出依赖,数据或 notebook 放在各自的文件夹中。测试文件命名为 test_*.py,这样 pytest 之类的工具才能找到。名字清晰、职责单一的小模块(pricing.py、loaders.py)比一个冗长的 utils.py 更容易测试和复用。
# pyproject.toml (names the project and its dependencies)
#
# [project]
# name = "quant-tools"
# version = "0.1.0"
# requires-python = ">=3.11"
# dependencies = ["numpy"]
# tests/test_pricing.py: found automatically because of the name
from quant.pricing import mid
def test_mid():
assert mid(184.0, 185.0) == 184.5在包内部导入
在包内部,可以从顶层用完整路径导入(from quant.pricing import mid,绝对导入),也可以相对当前模块导入(from .pricing import mid,一个点表示同一个包,两个点表示上一级,相对导入)。绝对导入通常更清晰。__init__.py 在包第一次被导入时运行,因此可以重新导出用户需要的名字(from .pricing import mid),这样 import quant; quant.mid(...) 就能用。运行包内代码要用 python -m quant.data.loaders,而不是 python quant/data/loaders.py,这样带点的名字才能被解析。
# quant/__init__.py: runs on the first "import quant"
from .pricing import mid # relative: same package
__all__ = ["mid"] # what "from quant import *" would give
# quant/data/loaders.py
from quant.pricing import mid # absolute: always starts at the top
from .. import pricing # relative: two dots = the parent package
# In a terminal, from the project folder:
# python -m quant.data.loaders (runs the module as part of the package)
# pytest -q (finds tests/test_*.py)# A file path and its module name are two spellings of one thing
path = "quant/data/loaders.py"
name = path[:-3].replace("/", ".")
print(name) # quant.data.loaders
print(name.split(".")[0]) # quant (the top-level package)
# __init__.py stands for its folder
print("quant/__init__.py"[:-3].replace("/", ".")) # quant.__init__ -> the package is "quant"__init__.py 的文件夹(“命名空间包”),但工具、测试运行器和安装程序对它的表现并不一致,在你电脑上能用的导入到别处可能失败。请给每个包文件夹都放一个 __init__.py。实验会让你找出包含代码却缺少它的文件夹。- 包是含有
__init__.py的文件夹;quant/data/loaders.py就是模块quant.data.loaders。 - 代码放在包里;测试、README、
pyproject.toml和数据放在旁边。 - 优先使用绝对导入;用
python -m package.module运行包内代码。
下一节:虚拟环境与 pip,让每个项目恰好拥有它所需的库。
实验:评分内容
- 用
module_name把文件路径转换为模块名25 分 - 用
find_modules列出项目中可导入的模块25 分 - 用
missing_init找出缺少 __init__.py 的文件夹25 分 - 用
import_line为模块写出导入语句25 分