Appearance
Python 模块与包
模块(Module)是一个 .py 文件,包(Package)是一个包含 __init__.py 的目录。它们是 Python 组织和复用代码的核心机制。
1. 模块基础
1.1 什么是模块
每个 .py 文件就是一个模块,模块名就是文件名(不含 .py)。
project/
├── main.py
├── utils.py ← 模块 utils
└── calculator.py ← 模块 calculatorpython
# calculator.py
def add(a, b):
return a + b
def subtract(a, b):
return a - b
PI = 3.141591.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)) # 81.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.pypython
# mypackage/math_utils.py
def square(x):
return x ** 2
def cube(x):
return x ** 3python
# 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 statistics2.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_wordspython
# 因为 __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 squarePython 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/False3.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 → null4.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/PM4.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 或 b4.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, x4.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/simple5.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.txt5.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 None6.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 termios7. 标准库速查表
| 模块 | 用途 | 常用功能 |
|---|---|---|
os | 操作系统接口 | 文件操作、环境变量、路径处理 |
sys | 系统参数 | 命令行参数、路径、退出 |
pathlib | 路径操作(推荐) | Path 对象、glob、读写文件 |
json | JSON 处理 | 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 |
csv | CSV 文件 | reader、writer、DictReader |
sqlite3 | SQLite 数据库 | connect、execute |
urllib | URL 处理 | urlparse、urlencode |
http | HTTP 服务 | 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