Skip to content

Python 模块与包

模块(Module)是一个 .py 文件,包(Package)是一个包含 __init__.py 的目录。它们是 Python 组织和复用代码的核心机制。

1. 模块基础

1.1 什么是模块

每个 .py 文件就是一个模块,模块名就是文件名(不含 .py)。

project/
├── main.py
├── utils.py        ← 模块 utils
└── calculator.py   ← 模块 calculator
python
# calculator.py
def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

PI = 3.14159

1.2 导入模块的方式

python
# ===== 方式 1:import 模块名 =====
import calculator

print(calculator.add(3, 5))       # 8
print(calculator.subtract(10, 3)) # 7
print(calculator.PI)              # 3.14159

# ===== 方式 2:import 模块名 as 别名 =====
import calculator as calc

print(calc.add(3, 5))    # 8

# ===== 方式 3:from 模块名 import 具体内容 =====
from calculator import add, PI

print(add(3, 5))   # 8     (直接使用,不需要模块名前缀)
print(PI)           # 3.14159

# ===== 方式 4:from 模块名 import * =====
from calculator import *

print(add(3, 5))       # 8
print(subtract(10, 3)) # 7
# ⚠️ 不推荐:可能导致命名冲突,不清楚导入了哪些名称

# ===== 方式 5:from 模块名 import 名称 as 别名 =====
from calculator import add as plus

print(plus(3, 5))  # 8

1.3 __all__ 控制导出

当使用 from module import * 时,__all__ 决定哪些名称会被导出。

python
# mymodule.py
__all__ = ["public_func", "PUBLIC_VAR"]

PUBLIC_VAR = "公开"
_PRIVATE_VAR = "私有"

def public_func():
    return "公开函数"

def _helper():
    return "内部辅助函数"

def another_func():
    return "这个不在 __all__ 中"
python
# main.py
from mymodule import *

print(public_func())     # 公开函数
print(PUBLIC_VAR)        # 公开
# print(another_func())  # ❌ NameError(不在 __all__ 中)
# print(_helper())       # ❌ NameError

# 但显式导入不受 __all__ 限制
from mymodule import another_func, _helper
print(another_func())    # 这个不在 __all__ 中
print(_helper())         # 内部辅助函数

1.4 __name____main__

每个模块都有 __name__ 属性。直接运行时为 "__main__",被导入时为模块名。

python
# greet.py
def hello(name):
    print(f"Hello, {name}!")

def main():
    hello("Alice")
    hello("Bob")

# 这个判断让模块既可以独立运行,也可以被安全导入
if __name__ == "__main__":
    # 只有直接运行 greet.py 时才会执行
    main()
python
# 直接运行 greet.py
# $ python greet.py
# Hello, Alice!
# Hello, Bob!

# 作为模块导入时,main() 不会自动执行
import greet
greet.hello("Charlie")  # Hello, Charlie!
print(greet.__name__)   # greet  (不是 __main__)

2. 包(Package)

2.1 包的结构

包是一个包含 __init__.py 的目录,可以嵌套形成多级包。

myproject/
├── main.py
└── mypackage/                  ← 包
    ├── __init__.py             ← 包的初始化文件
    ├── math_utils.py           ← 子模块
    ├── string_utils.py         ← 子模块
    └── advanced/               ← 子包
        ├── __init__.py
        └── statistics.py
python
# mypackage/math_utils.py
def square(x):
    return x ** 2

def cube(x):
    return x ** 3
python
# mypackage/string_utils.py
def capitalize_words(text):
    return " ".join(w.capitalize() for w in text.split())

def reverse(text):
    return text[::-1]

2.2 导入包中的模块

python
# 方式 1:导入子模块
import mypackage.math_utils

print(mypackage.math_utils.square(5))  # 25

# 方式 2:from 包 import 模块
from mypackage import math_utils

print(math_utils.cube(3))  # 27

# 方式 3:从子模块导入具体内容
from mypackage.math_utils import square
from mypackage.string_utils import capitalize_words

print(square(4))                      # 16
print(capitalize_words("hello world")) # Hello World

# 方式 4:导入子包
from mypackage.advanced import statistics

2.3 __init__.py 的作用

__init__.py 在包被导入时自动执行,常用于:

python
# mypackage/__init__.py

# 1. 定义包级别的变量
VERSION = "1.0.0"

# 2. 控制 from mypackage import * 的行为
__all__ = ["math_utils", "string_utils"]

# 3. 提供便捷导入(把子模块的内容提升到包层级)
from mypackage.math_utils import square, cube
from mypackage.string_utils import capitalize_words
python
# 因为 __init__.py 做了便捷导入,可以直接:
from mypackage import square, cube, capitalize_words, VERSION

print(square(5))       # 25
print(cube(3))         # 27
print(VERSION)         # 1.0.0

# 而不必写完整路径
# from mypackage.math_utils import square

Python 3.3+ 支持"命名空间包"(没有 __init__.py 的目录也能作为包),但普通项目建议始终加上 __init__.py

2.4 相对导入与绝对导入

python
# 假设包结构:
# mypackage/
#   __init__.py
#   module_a.py
#   module_b.py
#   sub/
#     __init__.py
#     module_c.py

# ----- 绝对导入(推荐)-----
# 在 module_b.py 中
from mypackage.module_a import some_func
from mypackage.sub.module_c import another_func

# ----- 相对导入(只能在包内部使用)-----
# 在 module_b.py 中
from .module_a import some_func        # . 表示当前包
from .sub.module_c import another_func  # 当前包的子包

# 在 sub/module_c.py 中
from ..module_a import some_func       # .. 表示上一级包
from . import __init__                 # . 当前子包

# ⚠️ 相对导入不能在顶层脚本(直接运行的文件)中使用
# 只能在作为包的一部分被导入时使用

3. 模块搜索路径

3.1 Python 查找模块的顺序

python
import sys

# Python 按以下顺序搜索模块:
# 1. 当前目录
# 2. PYTHONPATH 环境变量中的目录
# 3. 标准库目录
# 4. 第三方包目录(site-packages)

# 查看搜索路径
for path in sys.path:
    print(path)
# /home/user/project        (当前目录)
# /usr/lib/python3.12       (标准库)
# /usr/lib/python3.12/lib-dynload
# /home/user/.local/lib/python3.12/site-packages  (第三方包)

# 动态添加搜索路径
sys.path.append("/my/custom/path")

# 查看已导入的模块
print("json" in sys.modules)  # True/False

3.2 查看模块信息

python
import os

# 模块的文件路径
print(os.__file__)
# /usr/lib/python3.12/os.py

# 模块的所有属性和方法
print(dir(os))
# ['F_OK', 'O_APPEND', ..., 'path', 'remove', 'rename', ...]

# 模块的文档
print(os.__doc__[:80])
# OS routines for NT or Posix depending on what system we're on...

# 查看某个函数的帮助
help(os.path.join)

4. 常用标准库

4.1 os —— 操作系统接口

python
import os

# 当前工作目录
print(os.getcwd())   # /home/user/project

# 环境变量
print(os.getenv("HOME", "未设置"))   # /home/user
print(os.getenv("MY_VAR", "默认值")) # 默认值

# 文件和目录操作
os.makedirs("test_dir/sub", exist_ok=True)  # 递归创建目录
print(os.path.exists("test_dir"))     # True
print(os.path.isdir("test_dir"))      # True
print(os.path.isfile("test_dir"))     # False
print(os.listdir("test_dir"))         # ['sub']

# 路径操作
print(os.path.join("folder", "sub", "file.txt"))  # folder/sub/file.txt
print(os.path.basename("/home/user/test.py"))      # test.py
print(os.path.dirname("/home/user/test.py"))       # /home/user
print(os.path.splitext("data.tar.gz"))             # ('data.tar', '.gz')
print(os.path.abspath("test.py"))  # /home/user/project/test.py

# 清理
os.rmdir("test_dir/sub")
os.rmdir("test_dir")

4.2 sys —— 系统相关

python
import sys

# Python 版本
print(sys.version)       # 3.12.0 (main, Oct  2 2023, ...)
print(sys.version_info)  # sys.version_info(major=3, minor=12, micro=0, ...)

# 平台信息
print(sys.platform)    # linux / win32 / darwin

# 命令行参数
# $ python script.py arg1 arg2
print(sys.argv)  # ['script.py', 'arg1', 'arg2']

# 递归限制
print(sys.getrecursionlimit())  # 1000

# 对象大小(字节)
print(sys.getsizeof([]))        # 56
print(sys.getsizeof([1, 2, 3])) # 88

# 退出程序
# sys.exit(0)   # 正常退出
# sys.exit(1)   # 异常退出

4.3 pathlib —— 现代路径操作(推荐)

python
from pathlib import Path

# 创建路径对象
p = Path(".")
home = Path.home()              # 用户主目录
print(home)                     # /home/user

# 路径拼接(用 / 运算符,非常直观)
config = home / ".config" / "app" / "settings.json"
print(config)  # /home/user/.config/app/settings.json

# 路径属性
p = Path("/home/user/data/report.csv")
print(p.name)      # report.csv    文件名
print(p.stem)      # report        文件名(不含扩展名)
print(p.suffix)    # .csv          扩展名
print(p.parent)    # /home/user/data  父目录
print(p.parts)     # ('/', 'home', 'user', 'data', 'report.csv')

# 判断
print(Path(".").exists())     # True
print(Path(".").is_dir())     # True
print(Path("nonexist").exists())  # False

# 遍历目录
for f in Path(".").iterdir():
    print(f"{'[目录]' if f.is_dir() else '[文件]'} {f.name}")

# glob 模式匹配
# 当前目录下所有 .py 文件
for py_file in Path(".").glob("*.py"):
    print(py_file)

# 递归查找所有 .py 文件
for py_file in Path(".").rglob("*.py"):
    print(py_file)

# 读写文件(便捷方法)
p = Path("test_pathlib.txt")
p.write_text("Hello, pathlib!", encoding="utf-8")
content = p.read_text(encoding="utf-8")
print(content)  # Hello, pathlib!
p.unlink()      # 删除文件

# 创建目录
Path("a/b/c").mkdir(parents=True, exist_ok=True)

# 清理
Path("a/b/c").rmdir()
Path("a/b").rmdir()
Path("a").rmdir()

4.4 json —— JSON 处理

python
import json

# Python 对象 → JSON 字符串
data = {
    "name": "Alice",
    "age": 25,
    "hobbies": ["读书", "编程"],
    "address": {"city": "Beijing", "zip": "100000"},
    "active": True,
    "score": None
}

json_str = json.dumps(data, ensure_ascii=False, indent=2)
print(json_str)
# {
#   "name": "Alice",
#   "age": 25,
#   "hobbies": ["读书", "编程"],
#   "address": {
#     "city": "Beijing",
#     "zip": "100000"
#   },
#   "active": true,
#   "score": null
# }

# JSON 字符串 → Python 对象
parsed = json.loads(json_str)
print(parsed["name"])           # Alice
print(parsed["hobbies"])        # ['读书', '编程']
print(parsed["address"]["city"]) # Beijing
print(type(parsed["active"]))   # <class 'bool'>
print(parsed["score"])           # None

# 读写 JSON 文件
from pathlib import Path

# 写入
with open("data.json", "w", encoding="utf-8") as f:
    json.dump(data, f, ensure_ascii=False, indent=2)

# 读取
with open("data.json", "r", encoding="utf-8") as f:
    loaded = json.load(f)
print(loaded == data)  # True

Path("data.json").unlink()  # 清理

# 类型对应关系
# Python        JSON
# dict     →    object {}
# list     →    array  []
# str      →    string ""
# int/float →   number
# True/False →  true/false
# None     →    null

4.5 datetime —— 日期与时间

python
from datetime import datetime, date, time, timedelta

# 当前日期时间
now = datetime.now()
print(now)              # 2026-03-18 14:30:25.123456
print(now.year)         # 2026
print(now.month)        # 3
print(now.day)          # 18
print(now.hour)         # 14
print(now.weekday())    # 2  (0=周一,6=周日)

# 当前日期
today = date.today()
print(today)            # 2026-03-18

# 创建指定日期时间
dt = datetime(2024, 1, 15, 10, 30, 0)
print(dt)               # 2024-01-15 10:30:00

# 格式化输出
print(now.strftime("%Y年%m月%d日 %H:%M:%S"))  # 2026年03月18日 14:30:25
print(now.strftime("%Y-%m-%d"))                # 2026-03-18
print(now.strftime("%A"))                      # Wednesday

# 字符串解析为日期
dt = datetime.strptime("2024-06-15 08:30", "%Y-%m-%d %H:%M")
print(dt)  # 2024-06-15 08:30:00

# 时间差(timedelta)
tomorrow = today + timedelta(days=1)
last_week = today - timedelta(weeks=1)
print(tomorrow)     # 2026-03-19
print(last_week)    # 2026-03-11

# 两个日期的差
d1 = date(2024, 1, 1)
d2 = date(2024, 12, 31)
delta = d2 - d1
print(delta.days)   # 365

# 常用格式化代码
# %Y 四位年    %m 两位月    %d 两位日
# %H 24时      %M 分钟      %S 秒
# %A 星期全名  %a 星期缩写   %B 月份全名
# %I 12时      %p AM/PM

4.6 re —— 正则表达式

python
import re

text = "我的邮箱是 alice@example.com,电话是 13812345678"

# search() —— 查找第一个匹配
match = re.search(r"\d{11}", text)
if match:
    print(match.group())  # 13812345678

# findall() —— 查找所有匹配
emails = re.findall(r"[\w.]+@[\w.]+", text)
print(emails)  # ['alice@example.com']

# sub() —— 替换
result = re.sub(r"\d{11}", "***", text)
print(result)  # 我的邮箱是 alice@example.com,电话是 ***

# split() —— 按模式分割
parts = re.split(r"[,;,;\s]+", "苹果, 香蕉;西瓜 葡萄")
print(parts)  # ['苹果', '香蕉', '西瓜', '葡萄']

# 编译正则(多次使用时更高效)
pattern = re.compile(r"(\d{4})-(\d{2})-(\d{2})")
match = pattern.search("日期是 2024-06-15 和 2024-12-31")
if match:
    print(match.group())   # 2024-06-15  (完整匹配)
    print(match.group(1))  # 2024        (第 1 组)
    print(match.group(2))  # 06          (第 2 组)
    print(match.group(3))  # 15          (第 3 组)
    print(match.groups())  # ('2024', '06', '15')

# findall 配合分组
dates = pattern.findall("日期是 2024-06-15 和 2024-12-31")
print(dates)  # [('2024', '06', '15'), ('2024', '12', '31')]

# 常用正则速查
# .       任意字符(除换行)    \d  数字       \D  非数字
# \w      字母/数字/下划线      \s  空白字符    \S  非空白
# ^       开头                  $   结尾
# *       0 次或多次            +   1 次或多次   ?  0 或 1 次
# {n}     恰好 n 次             {n,m} n 到 m 次
# [abc]   字符集合              [^abc] 排除字符
# (...)   分组捕获              (?:...) 非捕获分组
# a|b     a 或 b

4.7 collections —— 高级数据结构

python
from collections import Counter, defaultdict, OrderedDict, deque, namedtuple

# ----- Counter:计数器 -----
words = "the quick brown fox jumps over the lazy dog the fox".split()
counter = Counter(words)
print(counter)                   # Counter({'the': 3, 'fox': 2, 'quick': 1, ...})
print(counter.most_common(3))    # [('the', 3), ('fox', 2), ('quick', 1)]
print(counter["the"])            # 3
print(counter["cat"])            # 0  (不存在返回 0)

# ----- defaultdict:带默认值的字典 -----
# 按首字母分组
words_list = ["apple", "banana", "avocado", "blueberry", "cherry", "apricot"]
groups = defaultdict(list)
for word in words_list:
    groups[word[0]].append(word)
print(dict(groups))
# {'a': ['apple', 'avocado', 'apricot'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}

# ----- deque:双端队列 -----
dq = deque([1, 2, 3])
dq.appendleft(0)    # 左侧添加
dq.append(4)        # 右侧添加
print(dq)            # deque([0, 1, 2, 3, 4])

dq.popleft()         # 左侧弹出,O(1)
dq.pop()             # 右侧弹出,O(1)
print(dq)            # deque([1, 2, 3])

# 固定长度(自动丢弃旧元素)
recent = deque(maxlen=3)
for i in range(5):
    recent.append(i)
    print(recent)
# deque([0], maxlen=3)
# deque([0, 1], maxlen=3)
# deque([0, 1, 2], maxlen=3)
# deque([1, 2, 3], maxlen=3)
# deque([2, 3, 4], maxlen=3)

# ----- namedtuple:命名元组 -----
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p.y)     # 3 4
print(p[0], p[1])   # 3 4
print(p._asdict())   # {'x': 3, 'y': 4}

4.8 itertools —— 迭代工具

python
import itertools

# chain —— 串联多个可迭代对象
result = list(itertools.chain([1, 2], [3, 4], [5]))
print(result)  # [1, 2, 3, 4, 5]

# product —— 笛卡尔积
result = list(itertools.product("AB", "12"))
print(result)  # [('A', '1'), ('A', '2'), ('B', '1'), ('B', '2')]

# combinations —— 组合(不重复)
result = list(itertools.combinations("ABCD", 2))
print(result)  # [('A','B'), ('A','C'), ('A','D'), ('B','C'), ('B','D'), ('C','D')]

# permutations —— 排列(有顺序)
result = list(itertools.permutations("ABC", 2))
print(result)  # [('A','B'), ('A','C'), ('B','A'), ('B','C'), ('C','A'), ('C','B')]

# groupby —— 分组(需要先排序)
data = [("水果", "苹果"), ("蔬菜", "白菜"), ("水果", "香蕉"), ("蔬菜", "萝卜")]
data.sort(key=lambda x: x[0])  # 先按类别排序

for key, group in itertools.groupby(data, key=lambda x: x[0]):
    items = [item[1] for item in group]
    print(f"{key}: {items}")
# 水果: ['苹果', '香蕉']
# 蔬菜: ['白菜', '萝卜']

# islice —— 切片迭代器
result = list(itertools.islice(range(100), 5, 10))
print(result)  # [5, 6, 7, 8, 9]

# count / cycle / repeat
# itertools.count(10)       # 10, 11, 12, 13, ...(无限)
# itertools.cycle("AB")     # A, B, A, B, ...(无限)
# itertools.repeat("x", 3)  # x, x, x

4.9 logging —— 日志

python
import logging

# 基本配置
logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s [%(levelname)s] %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)

# 日志级别(从低到高)
logging.debug("调试信息")       # DEBUG    —— 详细调试
logging.info("一般信息")        # INFO     —— 常规运行
logging.warning("警告信息")     # WARNING  —— 需要注意
logging.error("错误信息")       # ERROR    —— 出现错误
logging.critical("严重错误")    # CRITICAL —— 系统崩溃

# 输出:
# 2026-03-18 14:30:25 [DEBUG] 调试信息
# 2026-03-18 14:30:25 [INFO] 一般信息
# 2026-03-18 14:30:25 [WARNING] 警告信息
# 2026-03-18 14:30:25 [ERROR] 错误信息
# 2026-03-18 14:30:25 [CRITICAL] 严重错误

# 获取命名 logger(推荐,模块级别使用)
logger = logging.getLogger(__name__)
logger.info("使用命名 logger")

# 记录异常信息
try:
    1 / 0
except ZeroDivisionError:
    logger.exception("计算出错")
    # 会自动附带完整的异常堆栈信息

5. 第三方包管理

5.1 pip —— 包管理工具

bash
# 安装包
pip install requests
pip install requests==2.31.0          # 指定版本
pip install "requests>=2.28,<3.0"     # 版本范围

# 升级包
pip install --upgrade requests

# 卸载包
pip uninstall requests

# 查看已安装的包
pip list
pip show requests     # 查看包的详细信息

# 导出和安装依赖
pip freeze > requirements.txt          # 导出
pip install -r requirements.txt        # 安装

# 使用国内镜像加速
pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple

5.2 虚拟环境

每个项目使用独立的 Python 环境,避免包版本冲突。

bash
# ===== venv(内置)=====
# 创建虚拟环境
python -m venv .venv

# 激活
# Linux/macOS:
source .venv/bin/activate
# Windows:
.venv\Scripts\activate

# 激活后安装包(只在此环境中生效)
pip install requests flask

# 退出虚拟环境
deactivate

# ===== uv(推荐,更快速的现代工具)=====
# 安装 uv
pip install uv

# 创建虚拟环境
uv venv

# 安装包(比 pip 快 10-100 倍)
uv pip install requests flask

# 安装依赖
uv pip install -r requirements.txt

5.3 requirements.txt

# requirements.txt 示例
requests==2.31.0
flask>=3.0,<4.0
numpy~=1.26.0         # 兼容版本(>=1.26.0, <1.27.0)
python-dotenv          # 不指定版本(不推荐在生产环境中这样做)

# 版本约束符号
# ==    精确版本
# >=    最低版本
# <=    最高版本
# ~=    兼容版本
# !=    排除版本

5.4 pyproject.toml(现代项目配置)

toml
# pyproject.toml —— Python 项目的标准配置文件

[project]
name = "myproject"
version = "0.1.0"
description = "我的 Python 项目"
requires-python = ">=3.10"
dependencies = [
    "requests>=2.28",
    "flask>=3.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=7.0",
    "black",
    "ruff",
]

6. 模块的高级用法

6.1 动态导入

python
import importlib

# 根据字符串名称动态导入模块
module_name = "json"
mod = importlib.import_module(module_name)

data = mod.dumps({"key": "value"})
print(data)  # {"key": "value"}

# 实用场景:插件系统
def load_plugin(plugin_name):
    try:
        plugin = importlib.import_module(f"plugins.{plugin_name}")
        return plugin
    except ModuleNotFoundError:
        print(f"插件 {plugin_name} 未找到")
        return None

6.2 重新加载模块

python
import importlib
import mymodule

# 模块只会被导入一次,后续 import 使用缓存
# 开发调试时可以强制重新加载
importlib.reload(mymodule)

6.3 模块的特殊属性

python
import os

# 常用模块属性
print(os.__name__)     # os              模块名
print(os.__file__)     # /usr/.../os.py   文件路径
print(os.__doc__[:50]) # OS routines...   文档字符串
print(os.__package__)  # (空字符串,顶级模块)

# __all__  控制 from module import * 的导出列表
# __version__  常用于标记版本号(非内置,需自行定义)

# 查看模块的所有公开名称
public = [name for name in dir(os) if not name.startswith("_")]
print(public[:10])
# ['F_OK', 'O_APPEND', 'O_CREAT', 'O_EXCL', ...]

6.4 条件导入与延迟导入

python
# 条件导入:兼容不同环境
try:
    import ujson as json    # 优先使用更快的 ujson
except ImportError:
    import json             # 回退到标准库

data = json.dumps({"key": "value"})
print(data)  # {"key": "value"}

# 延迟导入:在需要时才导入,加速启动
def process_data(data):
    import pandas as pd  # 用到时才导入(pandas 导入较慢)
    df = pd.DataFrame(data)
    return df

# 平台相关导入
import sys
if sys.platform == "win32":
    import msvcrt
else:
    import termios

7. 标准库速查表

模块用途常用功能
os操作系统接口文件操作、环境变量、路径处理
sys系统参数命令行参数、路径、退出
pathlib路径操作(推荐)Path 对象、glob、读写文件
jsonJSON 处理dumps/loads、dump/load
re正则表达式search、findall、sub、split
datetime日期时间now、strftime、timedelta
collections高级容器Counter、defaultdict、deque
itertools迭代工具chain、product、combinations
functools函数工具lru_cache、partial、wraps
logging日志debug/info/warning/error
math数学函数sqrt、ceil、floor、pi
random随机数randint、choice、shuffle
hashlib哈希摘要md5、sha256
csvCSV 文件reader、writer、DictReader
sqlite3SQLite 数据库connect、execute
urllibURL 处理urlparse、urlencode
httpHTTP 服务HTTPServer
subprocess子进程run、Popen
threading多线程Thread、Lock
multiprocessing多进程Process、Pool
typing类型提示Optional、Union、List
dataclasses数据类@dataclass、field
enum枚举Enum、auto
copy拷贝copy、deepcopy
pprint美化打印pprint
argparse命令行解析ArgumentParser
unittest / pytest测试TestCase、assert
pickle序列化dump/load(Python 对象)
shutil高级文件操作copy、move、rmtree
tempfile临时文件NamedTemporaryFile
contextlib上下文管理contextmanager
abc抽象基类ABC、abstractmethod
textwrap文本换行wrap、dedent

8. 综合示例

示例 1:项目结构最佳实践

my_project/
├── pyproject.toml           # 项目配置
├── requirements.txt         # 依赖列表
├── README.md
├── src/
│   └── my_project/          # 源码包
│       ├── __init__.py
│       ├── main.py          # 入口
│       ├── config.py        # 配置
│       ├── models/          # 数据模型
│       │   ├── __init__.py
│       │   └── user.py
│       └── utils/           # 工具函数
│           ├── __init__.py
│           ├── file_utils.py
│           └── string_utils.py
├── tests/                   # 测试
│   ├── __init__.py
│   ├── test_models.py
│   └── test_utils.py
└── .venv/                   # 虚拟环境(不提交到 git)

示例 2:编写可复用的工具模块

python
# utils/file_utils.py
"""文件操作工具模块"""

import json
from pathlib import Path
from typing import Any

__all__ = ["read_json", "write_json", "ensure_dir"]

def read_json(filepath: str) -> Any:
    """读取 JSON 文件"""
    path = Path(filepath)
    if not path.exists():
        raise FileNotFoundError(f"文件不存在:{filepath}")
    return json.loads(path.read_text(encoding="utf-8"))

def write_json(filepath: str, data: Any, indent: int = 2) -> None:
    """写入 JSON 文件"""
    path = Path(filepath)
    ensure_dir(str(path.parent))
    path.write_text(
        json.dumps(data, ensure_ascii=False, indent=indent),
        encoding="utf-8",
    )

def ensure_dir(dirpath: str) -> Path:
    """确保目录存在,不存在则创建"""
    path = Path(dirpath)
    path.mkdir(parents=True, exist_ok=True)
    return path

# 模块自测
if __name__ == "__main__":
    # 只有直接运行此文件时才执行
    test_data = {"name": "测试", "items": [1, 2, 3]}

    write_json("test_output.json", test_data)
    loaded = read_json("test_output.json")
    print(loaded)  # {'name': '测试', 'items': [1, 2, 3]}
    assert loaded == test_data
    print("所有测试通过!")

    Path("test_output.json").unlink()

示例 3:简易配置管理

python
# config.py
"""基于环境变量和配置文件的配置管理"""

import os
import json
from pathlib import Path
from dataclasses import dataclass, field

@dataclass
class AppConfig:
    app_name: str = "MyApp"
    debug: bool = False
    host: str = "127.0.0.1"
    port: int = 8000
    db_url: str = "sqlite:///app.db"
    allowed_origins: list = field(default_factory=lambda: ["http://localhost:3000"])

    @classmethod
    def from_env(cls):
        """从环境变量加载配置"""
        return cls(
            app_name=os.getenv("APP_NAME", "MyApp"),
            debug=os.getenv("DEBUG", "false").lower() == "true",
            host=os.getenv("HOST", "127.0.0.1"),
            port=int(os.getenv("PORT", "8000")),
            db_url=os.getenv("DATABASE_URL", "sqlite:///app.db"),
        )

    @classmethod
    def from_json(cls, filepath: str):
        """从 JSON 文件加载配置"""
        path = Path(filepath)
        if path.exists():
            data = json.loads(path.read_text(encoding="utf-8"))
            return cls(**data)
        return cls()

# 使用
if __name__ == "__main__":
    # 默认配置
    config = AppConfig()
    print(f"应用:{config.app_name}")   # 应用:MyApp
    print(f"调试:{config.debug}")      # 调试:False
    print(f"地址:{config.host}:{config.port}")  # 地址:127.0.0.1:8000

    # 从环境变量加载
    os.environ["DEBUG"] = "true"
    os.environ["PORT"] = "3000"
    config = AppConfig.from_env()
    print(f"调试:{config.debug}")      # 调试:True
    print(f"端口:{config.port}")       # 端口:3000

Released under the MIT License.