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)

逐段拆解为什么这么写

新手最容易忽略的就是这些「看似没用但关键」的细节,每个部分都有意义:

  1. 顶部两行(可选但加分)

    • #!/usr/bin/env python3:Unix/Linux/macOS 上可以直接 chmod +x hello.py && ./hello.py 运行
    • # -*- coding: utf-8 -*-:Python 3 默认 UTF-8,但加上兼容性更好(比如旧工具、旧文档生成器)
  2. 模块级文档字符串(Docstring,必填!)

    • 模块的第一句/段必须是三引号包裹的说明
    • 要讲清楚:模块做什么、核心功能有哪些、有没有特殊使用场景(比如命令行)
    • 可以用 hello.__doc__help(hello) 查看
  3. 元信息变量(Public 特殊变量,必填)

    • __author____version____license__:让使用者快速知道「谁写的、哪个版本、能不能商用」
    • 版本号建议遵循语义化规范主版本.次版本.补丁版本,大改改主,加功能改次,修bug改补丁)
  4. 导入顺序(严格按 PEP 8)

    • 先导入标准库(比如 systyping
    • 再导入第三方库(比如 requestspandas
    • 最后导入本地模块
    • 每类之间空一行,同类按字母序排列(强迫症友好,编辑器也有自动排序插件)
  5. 常量与辅助/公开代码的顺序

    • 全局常量先写(全大写,下划线分隔,一眼就能找到)
    • 然后是仅内部用的私有函数/类(单下划线 _ 开头,提示「别乱碰,碰坏了不管」)
    • 最后放给外部用的公开函数/类(代码逻辑由底层到顶层,阅读更顺)
  6. 主入口 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, PI随便导入、随便用,是模块对外的「接口」
内部提示成员_build_message单下划线开头,只是提示外部不要用,但强行 import _build_messagegt._build_message 还是能调出来(属于「君子协定」)
名称修饰成员__secret_data双下划线开头,会触发 Python 的名称修饰(变成 _模块名__secret_data),更难直接访问,但也不是完全锁死(属于「防君子不防小人」)

名称修饰的小实验

我们在 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!")

这样改代码的时候,直接运行模块就能检查有没有破坏现有功能(正式测试建议用 pytestunittest)。

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)都有自动格式化插件(比如 blackautopep8),一键就能搞定。


好的模块是可复用代码的基石,今天讲的这些内容,足够应付大部分 Python 入门到进阶的开发场景了。下次写脚本的时候,记得试试这个模板呀!