你有没有经历过这种痛苦——打开一个 Python 文件,往下一拉,好家伙,3000 行,找个函数翻半天?
这就是今天要聊的话题:怎么把代码拆得明明白白。
一个 .py 文件就是一个模块
这个概念其实特别简单。你写了一个 utils.py,那 utils 就是一个模块。想在别的文件里用它?导入就行:
import utils
utils.some_function()
导入有三种写法,我直接说结论:**默认用 import xxx**。为什么?因为看到 math.sqrt() 你就知道 sqrt 来自 math 模块,一清二楚。要是写成 from math import sqrt,调用时只写 sqrt(),别人读代码还得猜这函数哪来的。
当然,from math import pi 这种导入一两个常量的情况也完全没问题。但 from math import * 这种"全倒入"的写法,千万别用——你不知道它会往你的代码里塞多少名字,万一跟你自己的变量撞了,debug 到哭。
还有一个特别实用的技巧:给模块起别名。比如 import numpy as np,这已经是整个 Python 社区的默契了,大家一看就懂。
Python 去哪找你的模块?
当你写下 import utils,Python 会按顺序在几个地方找这个文件:
这里面有个大坑:千万别给你的文件取跟标准库一样的名字。你要是写了个 json.py,同目录下 import json 导入的就是你自己的文件,而不是标准库的 json。这种 bug 找起来能让人怀疑人生。
那个经典的 if __name__ == "__main__"
这段代码你肯定见过:
if __name__ == "__main__":
main()
原理一句话就说清:文件被直接运行时,__name__ 等于 "__main__";被导入时,__name__ 等于模块名。所以这行代码的作用就是——直接运行才执行,被导入不执行。
养个好习惯,每个脚本底部都加上它。这样你的文件既能当脚本直接跑,又能当模块被别人导入,互不干扰。
目录也能当模块用——包(Package)
把几个 .py 文件放进一个目录,再加一个 __init__.py 文件(哪怕是空的),这个目录就变成了"包":
myapp/
├── __init__.py
├── models.py # myapp.models
├── views.py # myapp.views
└── utils.py # myapp.utils
__init__.py 不只是个占位符。你可以在里面导入子模块的核心功能,让用户写 from myapp import User 就够了,不用写 from myapp.models import User。这就是一种"便捷接口"的设计。
包还能嵌套——包里面再放包,形成子包。但记住一条:包层级别超过 3 层。太深的嵌套只会让导入路径变得又长又难读。
绝对导入 vs 相对导入
包内部的模块互相导入时,有两种写法:
# 绝对导入:从项目根包写完整路径
from myproject.models.user import User
# 相对导入:用 . 表示当前包,.. 表示上级包
from ..models.user import User
PEP 8 官方推荐绝对导入,理由很直白:一眼就能看出 User 在哪。相对导入还得在心里算一算 .. 是第几层,容易搞晕。
不过相对导入也有它的价值——当你可能改包名的时候,相对导入不需要改任何代码。很多大型项目内部用相对导入,对外接口用绝对导入,这个思路可以借鉴。
你可能不知道的标准库宝藏
Python 最大的优势之一就是"自带电池"——装完 Python 就有好几百个现成模块可以用。挑几个最实用的说说:
pathlib——路径操作首选。用 / 拼接路径,比 os.path.join 优雅太多:
from pathlib import Path
p = Path("src") / "main.py"
print(p.read_text()) # 直接读取文件内容
collections——高级容器。Counter 数数、defaultdict 省去判断 key 是否存在、deque 做双端队列,都是一行搞定的事。
json / csv——数据处理标配。json.dumps() 和 json.loads() 一对搭档走天下,CSV 读写也是几行代码的事。
secrets——生成安全随机数。别再用 random 模块生成密码了,secrets.token_hex(16) 才是正解。
dataclasses——用装饰器自动生成 __init__、__repr__、__eq__,省掉一大坨样板代码:
from dataclasses import dataclass
@dataclass
classUser:
name: str
age: int
email: str = ""
contextlib——@contextmanager 装饰器让你用生成器写上下文管理器,不用写类那么麻烦:
from contextlib import contextmanager
@contextmanager
deftimer(label="操作"):
start = time.time()
yield
print(f"{label}耗时 {time.time() - start:.2f} 秒")
with timer("数据处理"):
# 你的代码
pass
总结
模块和包是 Python 项目组织的基本功:.py 文件是模块,带 __init__.py 的目录是包,import 把它们串起来。标准库里有大量现成工具,遇到问题先想想"Python 是不是已经帮我造好了这个轮子"。
别想着一次把标准库全记住,那是查文档的事。你只需要建立一个索引——知道有什么,用的时候去查就行。Python 官方文档(docs.python.org/3/library/)写得很详细,是最好的参考。
把代码拆好了,你未来的自己会感谢现在的你。