Python 模块开发指南
你有没有过这种经历?写了个实用的小爬虫/工具脚本,越堆功能越臃肿——几百行混在一起,找个函数眼睛都要花,换个项目复用还得复制粘贴改半天?
从杂乱脚本到可复用代码的第一步,就是学会写规范的 Python 模块。今天这篇,我们从基础模板、到常见用法、再到封装与最佳实践,一步步教你写出清晰、专业、别人愿意用的模块。
模块基础
先重申下核心(也别嫌烦,很多人用了很久都踩了基础坑):Python 模块本质是带 .py 后缀的纯文本文件,文件名就是天然的模块名。它可以装函数、类、全局变量,甚至执行逻辑,但规范的模块只会把「一次性初始化/测试」放在最后。
一个通用的现代模块模板
直接上干货——这个模板兼容 Python 3.6+,符合 PEP 8/257/484 规范,拿来就能改:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""用户友好型问候模块
提供公开的 `greet()` 和 `farewell()` 函数,支持自定义问候语气。
也可以通过命令行直接调用:
- 无参数:输出通用问候
- 1个参数:输出指定人名的问候
"""
__author__ = "CodeDog2024"
__version__ = "0.1.0"
__license__ = "MIT"
# 导入区(模块文档后、全局变量前)
import sys
from typing import Optional
# 常量区(全大写、下划线分隔,尽量放在导入后)
_DEFAULT_GREETING = "Hello"
_DEFAULT_FAREWELL = "Goodbye"
# 私有辅助函数/类(单下划线开头,仅内部用)
def _build_message(template: str, name: Optional[str] = None) -> str:
"""内部辅助:根据模板和人名生成完整消息"""
if not name:
return f"{template}!"
return f"{template}, {name}!"
# 公开函数/类(普通命名,放在辅助之后)
def greet(name: Optional[str] = None, custom_template: Optional[str] = None) -> str:
"""生成正式/自定义的问候语
Args:
name: 要问候的对象名称,省略则输出通用问候
custom_template: 自定义问候模板,省略用默认 "Hello"
Returns:
格式化后的问候字符串
"""
template = custom_template or _DEFAULT_GREETING
return _build_message(template, name)
def farewell(name: Optional[str] = None, custom_template: Optional[str] = None) -> str:
"""生成正式/自定义的告别语
Args:
name: 要告别的对象名称,省略则输出通用告别
custom_template: 自定义告别模板,省略用默认 "Goodbye"
Returns:
格式化后的告别字符串
"""
template = custom_template or _DEFAULT_FAREWELL
return _build_message(template, name)
# 主入口区(仅当模块被直接执行时运行)
def main(args: list[str]) -> None:
"""处理命令行参数的主入口"""
name = args[1] if len(args) > 1 else None
print(greet(name))
if __name__ == "__main__":
main(sys.argv)
逐段拆解为什么这么写
新手最容易忽略的就是这些「看似没用但关键」的细节,每个部分都有意义:
-
顶部两行(可选但加分)
#!/usr/bin/env python3:Unix/Linux/macOS 上可以直接 chmod +x hello.py && ./hello.py 运行
# -*- coding: utf-8 -*-:Python 3 默认 UTF-8,但加上兼容性更好(比如旧工具、旧文档生成器)
-
模块级文档字符串(Docstring,必填!)
- 模块的第一句/段必须是三引号包裹的说明
- 要讲清楚:模块做什么、核心功能有哪些、有没有特殊使用场景(比如命令行)
- 可以用
hello.__doc__ 或 help(hello) 查看
-
元信息变量(Public 特殊变量,必填)
__author__、__version__、__license__:让使用者快速知道「谁写的、哪个版本、能不能商用」
- 版本号建议遵循语义化规范(
主版本.次版本.补丁版本,大改改主,加功能改次,修bug改补丁)
-
导入顺序(严格按 PEP 8)
- 先导入标准库(比如
sys、typing)
- 再导入第三方库(比如
requests、pandas)
- 最后导入本地模块
- 每类之间空一行,同类按字母序排列(强迫症友好,编辑器也有自动排序插件)
-
常量与辅助/公开代码的顺序
- 全局常量先写(全大写,下划线分隔,一眼就能找到)
- 然后是仅内部用的私有函数/类(单下划线
_ 开头,提示「别乱碰,碰坏了不管」)
- 最后放给外部用的公开函数/类(代码逻辑由底层到顶层,阅读更顺)
-
主入口 if __name__ == "__main__":(必学!)
- 模块被直接执行时,
__name__ 会自动变成 "__main__",触发后面的代码
- 模块被其他文件导入时,
__name__ 是模块名(比如 hello),主入口不会执行
- 通常用来写单元测试片段或命令行调用逻辑
模块的两种使用方式
方式1:作为独立脚本(命令行执行)
把刚才的模板存成 greet_tool.py,直接在终端试:
# Python 3.x 通用写法
$ python3 greet_tool.py
Hello!
# 带参数
$ python3 greet_tool.py Python
Hello, Python!
# Unix/Linux/macOS 可以先加执行权限
$ chmod +x greet_tool.py
$ ./greet_tool.py Alice
Hello, Alice!
方式2:作为可复用组件(导入到其他文件/交互环境)
基础导入(不污染命名空间)
# 完整导入
import greet_tool
# 调用公开函数
print(greet_tool.greet("Bob")) # Hello, Bob!
print(greet_tool.farewell("Bob", "See you next time")) # See you next time, Bob!
# 查看元信息
print(f"Version: {greet_tool.__version__}") # Version: 0.1.0
局部导入(只拿需要的)
# 只导入 greet 函数
from greet_tool import greet
print(greet("Charlie")) # Hello, Charlie!
# farewell 用不了,因为没导入
别名导入(解决命名冲突/简化长名)
# 给模块起别名
import greet_tool as gt
print(gt.greet("Dave"))
# 给函数起别名
from greet_tool import farewell as bye
print(bye("Eve"))
Python 的「伪私有」作用域控制
Python 没有像 Java/C++ 那样的 public/private/protected 关键字,全靠命名约定实现「软限制」:
三种命名约定的区别
名称修饰的小实验
我们在 greet_tool.py 里加个双下划线开头的变量:
# 在常量区后面加
__top_secret_key = "123456"
然后在交互环境试试:
import greet_tool
# 直接访问报错
print(greet_tool.__top_secret_key) # AttributeError: module 'greet_tool' has no attribute '__top_secret_key'
# 但知道修饰规则就能拿到
print(greet_tool._greet_tool__top_secret_key) # 123456
所以,一般情况下用单下划线提示内部使用就够了,双下划线只在「担心子类覆盖父类的同名方法」时用(面向对象进阶才会碰到)。
快速写出好模块的5个最佳实践
1. 功能要「单一、专一」
一个模块最好只做一件事,比如:
data_parser.py 只负责解析数据,别写爬虫逻辑
logger_util.py 只负责日志记录,别配数据库连接
如果模块超过了500-800行,或者出现了多个不相关的功能组,就该拆成包(Package)了(不过包的内容今天先不讲)。
2. 文档字符串和类型注解「双管齐下」
- 文档字符串(Docstring):给人看的,讲清楚「怎么用、参数是什么、返回什么、有没有异常」
- 类型注解(Type Hints):给编辑器、静态检查工具(比如 mypy)和自己看的,写代码时会有自动补全,还能提前发现类型错误
推荐用 Google 风格或NumPy 风格的 Docstring,简洁明了。
3. 用主入口放「快速测试代码」
写公开函数的时候,可以随手在 if __name__ == "__main__": 里加几行测试:
if __name__ == "__main__":
# 快速测试逻辑
assert greet() == "Hello!"
assert greet("Frank") == "Hello, Frank!"
assert greet("Frank", "Hi") == "Hi, Frank!"
print("All quick tests passed!")
这样改代码的时候,直接运行模块就能检查有没有破坏现有功能(正式测试建议用 pytest 或 unittest)。
4. 可选依赖用 try-except 包裹
如果你的模块有可选功能需要第三方库(比如只有生成图表的时候才需要 matplotlib),别直接在导入区写,会导致没有安装第三方库的人连基础功能都用不了:
# 错误写法
import matplotlib.pyplot as plt # 没装 matplotlib 就报错
# 正确写法
_MATPLOTLIB_AVAILABLE = False
try:
import matplotlib.pyplot as plt
_MATPLOTLIB_AVAILABLE = True
except ImportError:
pass
def plot_something():
if not _MATPLOTLIB_AVAILABLE:
raise ImportError("Please install matplotlib: pip install matplotlib")
# 绘图逻辑
5. 遵循 PEP 8 代码风格
PEP 8 是 Python 官方的代码风格指南,核心要求比如:
- 缩进用4个空格(别用Tab,不同编辑器Tab宽度不一样)
- 每行不超过79/120个字符(太长的话用括号换行)
- 运算符前后加空格(
a = b + c,别写 a=b+c)
- 函数/类之间空2行,函数内部逻辑空1行
现在的 Python 编辑器(VS Code、PyCharm)都有自动格式化插件(比如 black、autopep8),一键就能搞定。
好的模块是可复用代码的基石,今天讲的这些内容,足够应付大部分 Python 入门到进阶的开发场景了。下次写脚本的时候,记得试试这个模板呀!