Skip to content

02-自定义模块

Python 3.11+

概念铺垫

2.1 创建自己的模块

实际场景

你在多个项目中都要处理用户数据:验证邮箱、格式化手机号、计算年龄。每次都复制粘贴代码很麻烦,而且容易出错。

问题:如何把常用功能打包成模块,方便复用?

概念说明

任何 .py 文件都可以作为模块被导入。

2.2 模块的结构

┌─────────────────────────────────────────────────────────────┐
│              推荐的模块结构                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   # 1. 模块文档字符串                                       │
│   """模块说明"""                                            │
│                                                             │
│   # 2. 导入语句                                             │
│   from __future__ import annotations                        │
│   import os                                                 │
│   from typing import List, Optional                         │
│                                                             │
│   # 3. 模块级常量                                           │
│   VERSION = '1.0.0'                                         │
│   DEFAULT_TIMEOUT = 30                                      │
│                                                             │
│   # 4. 类定义                                               │
│   class MyClass:                                            │
│       pass                                                  │
│                                                             │
│   # 5. 函数定义                                             │
│   def my_function() -> None:                                │
│       pass                                                  │
│                                                             │
│   # 6. 测试代码                                             │
│   if __name__ == '__main__':                                │
│       # 测试逻辑                                            │
│       pass                                                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

模块搜索路径

sys.path

Python 按照特定顺序搜索模块。

搜索顺序:

┌─────────────────────────────────────────────────────────────┐
│              模块搜索顺序                                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   1. 当前目录                                               │
│      运行脚本的所在目录                                     │
│                                                             │
│   2. PYTHONPATH 环境变量                                    │
│      系统环境变量指定的目录                                 │
│                                                             │
│   3. 标准库目录                                             │
│      Python 安装目录下的库                                  │
│                                                             │
│   4. site-packages                                          │
│      pip 安装的第三方库                                     │
│                                                             │
│   5. .pth 文件                                              │
│      site-packages 中的路径配置文件                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

模块的私有成员

python
# my_module.py

# 约定的私有变量(单下划线)
_internal_var: str = "内部使用"

# 强私有变量(双下划线,会被名称改写)
__private_var: str = "强私有"

# 约定的私有函数
def _internal_function() -> None:
    """内部函数,不建议外部调用"""
    pass

# 公开函数
def public_function() -> str:
    """公开函数"""
    return "public"

使用建议:

python
import my_module

# 可以访问但不推荐
print(my_module._internal_var)

# 公开接口
my_module.public_function()

模块的懒加载

python
# lazy_module.py
from __future__ import annotations
from typing import Any
import time

def expensive_initialization() -> dict[str, Any]:
    """耗时的初始化"""
    time.sleep(2)
    return {"data": "initialized"}

# 懒加载:只在需要时初始化
_cache: dict[str, Any] | None = None

def get_data() -> dict[str, Any]:
    global _cache
    if _cache is None:
        _cache = expensive_initialization()
    return _cache

L1 理解层:会用

基本模块

python
# user_utils.py
"""用户数据处理工具模块"""
from __future__ import annotations
from typing import Optional
import re
from datetime import datetime

def validate_email(email: str) -> bool:
    """验证邮箱格式
    
    Args:
        email: 邮箱地址
        
    Returns:
        True 如果格式正确,否则 False
    """
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return bool(re.match(pattern, email))

def format_phone(phone: str) -> str:
    """格式化手机号为标准格式
    
    Args:
        phone: 手机号码字符串
        
    Returns:
        格式化后的手机号,如 "138-1234-5678"
    """
    digits = ''.join(filter(str.isdigit, phone))
    if len(digits) == 11:
        return f"{digits[:3]}-{digits[3:7]}-{digits[7:]}"
    return phone

def calculate_age(birth_year: int) -> int:
    """根据出生年份计算年龄
    
    Args:
        birth_year: 出生年份
        
    Returns:
        当前年龄
    """
    current_year = datetime.now().year
    return current_year - birth_year

if __name__ == '__main__':
    # 测试代码
    print(validate_email("test@example.com"))  # True
    print(format_phone("13812345678"))         # 138-1234-5678
    print(calculate_age(1990))                 # 36 (2026年)

导入使用:

python
# main.py
import user_utils

result = user_utils.validate_email("test@example.com")
print(result)  # True

age = user_utils.calculate_age(1990)
print(age)  # 36

sys.path 使用

python
import sys

# 查看模块搜索路径
for i, path in enumerate(sys.path):
    print(f"{i}: {path}")

输出示例:

0: /Users/user/project        # 当前目录
1: /usr/lib/python3.11        # 标准库目录
2: /usr/lib/python3.11/site-packages  # 第三方库目录
3: /Users/user/.local/lib/python3.11/site-packages

添加自定义路径

python
import sys

# 方式 1:临时添加
sys.path.append('/path/to/my/modules')
import my_module

# 方式 2:使用 .pth 文件
# 在 site-packages 目录创建 my_paths.pth
# 内容:/path/to/my/modules

# 方式 3:设置 PYTHONPATH 环境变量
# export PYTHONPATH=/path/to/my/modules:$PYTHONPATH

__all__ 变量

__all__ 定义了 from module import * 时导入的内容。

python
# my_module.py
from __future__ import annotations

__all__ = ['func_a', 'func_b', 'MyClass']

def func_a() -> str:
    return "A"

def func_b() -> str:
    return "B"

def _internal_func() -> str:  # 不在 __all__ 中
    return "internal"

class MyClass:
    pass

# 使用
from my_module import *
print(func_a())  # A
print(func_b())  # B
# _internal_func()  # NameError: 未导入

文档字符串

python
"""计算器模块

提供基本的数学运算功能。

Example:
    >>> from calculator import add
    >>> add(1, 2)
    3

Attributes:
    PI (float): 圆周率常量
    E (float): 自然常数
"""

def add(a: int, b: int) -> int:
    """两数相加
    
    Args:
        a: 第一个数
        b: 第二个数
    
    Returns:
        两数之和
    
    Raises:
        TypeError: 参数类型错误
    
    Example:
        >>> add(1, 2)
        3
    """
    return a + b

类型提示

python
from __future__ import annotations
from typing import Optional

def process_data(
    items: list[dict[str, str | int]],
    threshold: int | None = None
) -> dict[str, int]:
    """处理数据"""
    result: dict[str, int] = {}
    for item in items:
        key = item.get('name', '')
        value = item.get('value', 0)
        if threshold is None or value >= threshold:
            result[key] = value
    return result

L2 实践层:用好

推荐做法

做法原因示例
模块顶部写文档字符串帮助自己和他人理解模块用途"""用户数据处理工具模块"""
使用类型提示标注参数和返回值提高代码可读性和 IDE 支持def validate_email(email: str) -> bool:
用单下划线约定私有成员告知使用者这是内部实现def _internal_helper():
模块级常量用大写命名区分常量与变量VERSION = "1.0.0"
懒加载昂贵初始化减少导入时的开销延迟到函数调用时初始化
测试代码放在 if __name__ == "__main__"模块被导入时不执行测试见上方 user_utils.py 示例
使用 from __future__ import annotations避免类型注解的循环依赖和运行时开销Python 3.11+ 建议启用

反模式:不要这样做

python
# ❌ 没有模块文档
# mysterious.py
def f(x):
    return x * 2

# ✅ 添加文档,说明模块和函数的功能
"""数据处理工具模块"""
def double_value(x: int) -> int:
    """将输入值翻倍"""
    return x * 2

# ---

# ❌ 模块顶层执行副作用
# bad_module.py
import requests
CONFIG = requests.get("https://api.example.com/config").json()
# 每次导入都会发起网络请求!慢且不可靠

# ✅ 延迟获取配置
# good_module.py
import requests
from functools import lru_cache

@lru_cache(maxsize=1)
def get_config() -> dict:
    return requests.get("https://api.example.com/config").json()

# ---

# ❌ 忽略 `if __name__ == "__main__"` 保护
# no_guard.py
def setup():
    print("初始化资源...")
setup()  # 导入时就执行,可能破坏导入者的预期

# ✅ 加上保护
# with_guard.py
def setup():
    print("初始化资源...")
if __name__ == "__main__":
    setup()

# ---

# ❌ 模块中混用不同职责的功能
# kitchen_sink.py
def validate_email(email): ...      # 验证
def format_phone(phone): ...        # 格式化
def connect_database(): ...         # 数据库
def send_notification(): ...        # 通知
# 什么都做,难以维护和复用

# ✅ 按职责拆分
# validators.py  — 验证相关
# formatters.py  — 格式化相关
# database.py    — 数据库相关
# notifications.py — 通知相关

适用场景

场景推荐做法原因
多个项目共享工具函数打包为独立模块,放在公共路径下避免复制粘贴,统一维护
模块需要配置文件使用懒加载或依赖注入避免导入时读取文件阻塞
开发阶段频繁修改模块使用 importlib.reload()无需重启 Python 进程
发布为第三方库设置 __all__ 控制公开 API明确对外接口,隐藏实现细节
大型模块(>500行)拆分为多个子模块,用包组织提高可维护性和可读性
类型检查需要跨模块引用使用 TYPE_CHECKING + 延迟导入避免循环导入

L3 专家层:深入

Python 如何实现:模块对象的内部结构

CPython 中每个模块在运行时都有一个 PyModuleObject 结构体:

┌──────────────────────────────────────────────────────────────┐
│              CPython 模块对象 (PyModuleObject)               │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│   typedef struct {                                           │
│       PyObject_HEAD                                          │
│       PyObject *md_dict;    // __dict__ → 模块命名空间       │
│   } PyModuleObject;                                          │
│                                                              │
│   模块的执行过程:                                           │
│   ┌─────────────────────────────────────────────────────┐   │
│   │                                                      │   │
│   │   import user_utils                                 │   │
│   │     │                                                │   │
│   │     ├── 1. 创建空模块对象 module                     │   │
│   │     │      module = PyModule_New("user_utils")       │   │
│   │     │                                                │   │
│   │     ├── 2. 设置模块属性                             │   │
│   │     │      module.__name__ = "user_utils"            │   │
│   │     │      module.__file__ = "user_utils.py"         │   │
│   │     │      module.__package__ = "" or "pkg_name"     │   │
│   │     │      module.__loader__ = SourceFileLoader()    │   │
│   │     │                                                │   │
│   │     ├── 3. 初始化模块的 __dict__                      │   │
│   │     │      PyModule_GetDict(module) → md_dict        │   │
│   │     │                                                │   │
│   │     ├── 4. 执行模块源代码                            │   │
│   │     │      exec(code_object, module.__dict__)        │   │
│   │     │      # 所有顶层定义的变量/函数/类都写入 md_dict│   │
│   │     │                                                │   │
│   │     └── 5. 存入缓存                                  │   │
│   │            sys.modules["user_utils"] = module         │   │
│   │                                                      │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                              │
└──────────────────────────────────────────────────────────────┘

__name__ == "__main__" 的深入机制

┌──────────────────────────────────────────────────────────────┐
│            __name__ 在直接运行 vs 导入时的不同值              │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│   直接运行:python my_module.py                              │
│   ┌─────────────────────────────────────────────────────┐   │
│   │                                                      │   │
│   │   1. Python 解释器启动                              │   │
│   │   2. 创建一个主模块 (__main__)                      │   │
│   │   3. 读取 my_module.py 源码                         │   │
│   │   4. 编译并在 __main__ 的命名空间中执行             │   │
│   │   5. module.__name__ = "__main__"  ← 关键!         │   │
│   │   6. if __name__ == "__main__": → True              │   │
│   │                                                      │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                              │
│   作为模块导入:import my_module                              │
│   ┌─────────────────────────────────────────────────────┐   │
│   │                                                      │   │
│   │   1. import 语句触发                                 │   │
│   │   2. 创建模块对象 module = PyModule_New(...)        │   │
│   │   3. module.__name__ = "my_module"  ← 关键!         │   │
│   │   4. 编译并执行 my_module.py                        │   │
│   │   5. if __name__ == "__main__": → False             │   │
│   │   6. 测试代码被跳过                                  │   │
│   │                                                      │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                              │
└──────────────────────────────────────────────────────────────┘

验证代码:

python
# test_name.py
import sys

print(f"__name__ = {__name__!r}")
print(f"__file__ = {__file__!r}")
print(f"__package__ = {__package__!r}")

# 检查 sys.modules 中的主模块
main_module = sys.modules.get("__main__")
if main_module:
    print(f"sys.modules['__main__'] 存在")
    print(f"  __name__ = {main_module.__name__!r}")
    print(f"  __file__ = {getattr(main_module, '__file__', None)!r}")

# 直接运行输出:
# __name__ = '__main__'
# __file__ = 'test_name.py'
# __package__ = None

# import test_name 输出:
# __name__ = 'test_name'
# __file__ = '.../test_name.py'
# __package__ = ''

模块对象的完整属性

python
import importlib
import inspect

# 创建一个简单的模块来观察其内部
import types

mod = types.ModuleType("my_dynamic_module")
mod.__doc__ = "动态创建的模块"
mod.__package__ = None

# 模块的关键属性
print("模块属性:")
for attr in [
    "__name__", "__doc__", "__file__", "__dict__",
    "__package__", "__loader__", "__spec__",
    "__path__", "__annotations__"
]:
    value = getattr(mod, attr, "<未设置>")
    print(f"  {attr:20s} = {value!r}")

# 观察实际模块
import math
print(f"\nmath 模块的类型: {type(math)}")
print(f"math 是 ModuleType: {isinstance(math, types.ModuleType)}")
print(f"math.__dict__ 键数量: {len(math.__dict__)}")
print(f"math.__dict__ 前5个键: {list(math.__dict__.keys())[:5]}")

性能考量

操作复杂度说明
创建空模块对象O(1)PyModule_New() 只是分配一个结构体
执行模块代码O(代码行数 + 执行逻辑)取决于模块中顶层代码的复杂度
module.__dict__ 查找O(1) 平均底层是哈希字典
dir(module)O(n)n = 模块中定义的名称数量
懒加载初始化O(1) 首次检查 + O(初始化) 按需if _cache is None 模式
模块级常量赋值O(1)导入时立即赋值,无额外开销

知识关联

┌──────────────────────────────────────────────────────────────┐
│                    自定义模块 知识关联图                       │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│   本章(自定义模块)                                         │
│     │                                                        │
│     ├── 模块结构规范 ────────► 06-发布自己的包(pyproject)  │
│     │                                                        │
│     ├── sys.path ───────────► 01-模块基础(搜索路径)        │
│     │                                                        │
│     ├── __all__ ────────────► 03-包的结构(包级 __all__)    │
│     │                                                        │
│     ├── 私有成员 _name ─────► OOP(封装原则)                │
│     │                                                        │
│     ├── 懒加载模式 ─────────► 性能优化(延迟初始化)         │
│     │                                                        │
│     ├── __name__ =="__main__" → 01-模块基础(__name__)     │
│     │                                                        │
│     └── 模块对象类型 ────────► 01-模块基础(元类/反射)      │
│                                                              │
└──────────────────────────────────────────────────────────────┘

本章小结

┌─────────────────────────────────────────────────────────────┐
│                      自定义模块 知识要点                      │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   创建模块:                                                 │
│   ✓ 任何 .py 文件都是模块                                   │
│   ✓ 推荐结构:文档 → 导入 → 常量 → 类 → 函数 → 测试          │
│                                                             │
│   搜索路径:                                                 │
│   ✓ sys.path 列表                                           │
│   ✓ 顺序:当前目录 → PYTHONPATH → 标准库 → site-packages    │
│   ✓ 添加路径:sys.path.append()、.pth 文件                  │
│                                                             │
│   高级特性:                                                 │
│   ✓ __all__:控制 import * 的内容                           │
│   ✓ _name:约定的私有成员                                   │
│   ✓ 懒加载:延迟初始化                                      │
│                                                             │
│   最佳实践:                                                 │
│   ✓ 文档字符串                                              │
│   ✓ 类型提示                                                │
│   ✓ 测试代码放在 if __name__ == '__main__'                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘