Python 模块与包
目录
模块基础
什么是模块?
模块是一个包含 Python 代码的文件(.py 文件),可以包含:
- 变量
- 函数
- 类
- 可执行代码
模块的作用:
- 代码组织和复用
- 命名空间管理
- 提高代码可维护性
# math_operations.py - 一个简单的模块
"""数学运算模块"""
def add(a, b):
"""加法"""
return a + b
def subtract(a, b):
"""减法"""
return a - b
def multiply(a, b):
"""乘法"""
return a * b
def divide(a, b):
"""除法"""
if b == 0:
raise ValueError("除数不能为零")
return a / b
# 模块级别的变量
PI = 3.141592653589793
E = 2.718281828459045
使用模块
# main.py - 使用上面定义的模块
import math_operations
result = math_operations.add(10, 5)
print(result) # 15
print(math_operations.PI) # 3.141592653589793
导入模块
import 语句
# 方法1:导入整个模块
import math
print(math.sqrt(16)) # 4.0
print(math.pi) # 3.141592653589793
# 方法2:从模块导入特定内容
from math import sqrt, pi
print(sqrt(16)) # 4.0
print(pi) # 3.141592653589793
# 方法3:导入所有内容(不推荐)
from math import *
print(sqrt(16))
print(pi)
# 方法4:给模块起别名
import numpy as np
import pandas as pd
导入的区别
# 示例模块 my_module.py
VALUE = 100
def get_value():
return VALUE
def set_value(new_value):
global VALUE
VALUE = new_value
# 测试不同的导入方式
# 方式1:import module
import my_module
print(my_module.VALUE) # 100
my_module.set_value(200)
print(my_module.VALUE) # 200(修改生效)
# 方式2:from module import name
from my_module import VALUE, get_value
print(VALUE) # 100
# VALUE = 300 # 这只是创建了局部变量,不影响模块
print(get_value()) # 100(仍然是原始值)
条件导入
# 根据条件导入不同的模块
import sys
if sys.version_info >= (3, 9):
from collections import OrderedDict
else:
# 旧版本的兼容处理
pass
# 尝试导入,失败则使用替代方案
try:
import ujson as json
except ImportError:
import json
延迟导入
# 在函数内部导入,减少启动时间
def heavy_operation():
import numpy as np # 只在需要时导入
return np.array([1, 2, 3])
# 或者在函数开始时导入
def process_data(data):
import pandas as pd
df = pd.DataFrame(data)
return df
模块搜索路径
sys.path
import sys
# 查看模块搜索路径
for path in sys.path:
print(path)
# 输出示例:
# /Users/username/project
# /usr/local/lib/python3.9/site-packages
# /usr/local/lib/python3.9
# ...
添加搜索路径
import sys
# 方法1:运行时添加(临时)
sys.path.append('/path/to/my/modules')
sys.path.insert(0, '/path/to/my/modules') # 优先搜索
# 方法2:设置环境变量(永久)
# export PYTHONPATH=/path/to/my/modules:$PYTHONPATH
# 方法3:使用 .pth 文件
# 在 site-packages 目录创建 mypath.pth 文件
# 文件中写入路径,每行一个
模块查找顺序
- 内置模块
- 当前目录
PYTHONPATH环境变量- 安装目录(site-packages)
# 检查模块来源
import os
print(os.__file__) # 显示模块文件路径
import sys
print(sys.__file__) # None(内置模块)
创建模块
基本模块结构
# utils.py
"""
工具函数模块
这个模块提供常用的工具函数。
"""
# 模块文档字符串
__author__ = "Your Name"
__version__ = "1.0.0"
# 公开的内容
def format_date(date_obj):
"""格式化日期"""
return date_obj.strftime("%Y-%m-%d")
def validate_email(email):
"""验证邮箱格式"""
return "@" in email and "." in email
# 私有内容(约定:以下划线开头)
def _internal_helper():
"""内部辅助函数,不建议外部使用"""
pass
# 控制 from module import * 导入的内容
__all__ = ['format_date', 'validate_email']
__name__ 和 __main__
# calculator.py
"""计算器模块"""
def add(a, b):
return a + b
def multiply(a, b):
return a * b
# 当直接运行此文件时执行
if __name__ == "__main__":
# 测试代码
print("测试计算器模块")
print(f"add(2, 3) = {add(2, 3)}")
print(f"multiply(2, 3) = {multiply(2, 3)}")
# 当被导入时,上面的代码不会执行
# 直接运行
$ python calculator.py
测试计算器模块
add(2, 3) = 5
multiply(2, 3) = 6
# 导入使用
$ python
>>> import calculator
>>> calculator.add(2, 3)
5
模块缓存
# Python 会缓存已导入的模块
import sys
import my_module
print(id(my_module)) # 内存地址
import my_module # 再次导入
print(id(my_module)) # 相同的内存地址(缓存)
# 查看已缓存的模块
print(sys.modules.keys())
# 强制重新加载(开发时使用)
import importlib
importlib.reload(my_module)
包基础
什么是包?
包是包含多个模块的目录,必须包含 __init__.py 文件(Python 3.3+ 可选)。
my_package/
├── __init__.py # 包初始化文件
├── module1.py # 模块1
├── module2.py # 模块2
└── subpackage/ # 子包
├── __init__.py
└── module3.py
创建包
math_utils/
├── __init__.py
├── arithmetic.py
├── geometry.py
└── statistics.py
# math_utils/__init__.py
"""数学工具包"""
__version__ = "1.0.0"
# 从子模块导入常用功能
from .arithmetic import add, subtract, multiply, divide
from .geometry import circle_area, rectangle_area
# 控制包的公开接口
__all__ = [
'add', 'subtract', 'multiply', 'divide',
'circle_area', 'rectangle_area'
]
# math_utils/arithmetic.py
"""算术运算"""
def add(a, b):
return a + b
def subtract(a, b):
return a - b
def multiply(a, b):
return a * b
def divide(a, b):
if b == 0:
raise ValueError("除数不能为零")
return a / b
# math_utils/geometry.py
"""几何计算"""
import math
def circle_area(radius):
"""圆的面积"""
return math.pi * radius ** 2
def rectangle_area(width, height):
"""矩形面积"""
return width * height
def triangle_area(base, height):
"""三角形面积"""
return 0.5 * base * height
使用包
# 方法1:导入整个包
import math_utils
result = math_utils.add(10, 5)
# 方法2:从包导入特定模块
from math_utils import arithmetic
result = arithmetic.multiply(3, 4)
# 方法3:从包导入特定函数
from math_utils import add, circle_area
print(add(10, 5))
print(circle_area(5))
# 方法4:给包起别名
import math_utils as mu
result = mu.subtract(10, 5)
包的导入
绝对导入
# 项目结构
project/
├── main.py
└── package/
├── __init__.py
├── module_a.py
└── module_b.py
# package/module_a.py
def func_a():
return "Function A"
# package/module_b.py
# 绝对导入
from package.module_a import func_a
def func_b():
return func_a() + " and Function B"
# main.py
from package.module_b import func_b
print(func_b()) # Function A and Function B
包的 __init__.py
# package/__init__.py
"""包的初始化文件"""
# 1. 包元信息
__version__ = "1.0.0"
__author__ = "Your Name"
# 2. 导入子模块,简化使用
from .module_a import func_a
from .module_b import func_b
# 3. 初始化代码
print("包已加载")
# 4. 控制公开接口
__all__ = ['func_a', 'func_b']
# 使用时更简洁
import package
print(package.func_a())
print(package.func_b())
子包结构
web_framework/
├── __init__.py
├── core/
│ ├── __init__.py
│ ├── router.py
│ └── middleware.py
├── templates/
│ ├── __init__.py
│ └── renderer.py
└── database/
├── __init__.py
├── models.py
└── connection.py
# web_framework/core/router.py
class Router:
def __init__(self):
self.routes = {}
def add_route(self, path, handler):
self.routes[path] = handler
# web_framework/__init__.py
from .core.router import Router
from .database.connection import Database
__all__ = ['Router', 'Database']
# 使用
from web_framework import Router, Database
router = Router()
db = Database()
相对导入
相对导入语法
# 在项目内部使用相对导入
# package/subpackage/module.py
# 同级模块
from . import sibling_module
from .sibling_module import some_function
# 父级模块
from .. import parent_module
from ..parent_module import some_function
# 兄弟包的模块
from ..other_package import module
相对导入示例
project/
├── main.py
└── app/
├── __init__.py
├── models.py
├── views.py
└── utils/
├── __init__.py
└── helpers.py
# app/models.py
class User:
def __init__(self, name):
self.name = name
# app/utils/helpers.py
def format_user(user):
return f"User: {user.name}"
# app/views.py
# 相对导入
from .models import User # 同级模块
from .utils.helpers import format_user # 子包模块
def show_user(name):
user = User(name)
return format_user(user)
# main.py
# 使用绝对导入
from app.views import show_user
print(show_user("Alice")) # User: Alice
相对导入的限制
# ❌ 错误:不能在顶级脚本中使用相对导入
# main.py
from .app.models import User # ImportError
# ✅ 正确:使用绝对导入
from app.models import User
命名空间包
什么是命名空间包?
命名空间包允许将同一个包分布在多个目录中。
# 目录结构
namespace_pkg/
├── part1/
│ └── namespace_pkg/
│ └── module1.py
└── part2/
└── namespace_pkg/
└── module2.py
# 注意:命名空间包不需要 __init__.py
# 使用
import namespace_pkg.module1
import namespace_pkg.module2
PEP 420 隐式命名空间包
Python 3.3+ 支持隐式命名空间包(没有 __init__.py 的目录)。
常用内置模块
os 模块 - 操作系统接口
import os
# 文件和目录操作
print(os.getcwd()) # 当前工作目录
os.chdir('/path/to/dir') # 改变目录
print(os.listdir('.')) # 列出目录内容
# 路径操作
path = os.path.join('dir', 'subdir', 'file.txt')
print(os.path.exists(path)) # 检查是否存在
print(os.path.isfile(path)) # 是否是文件
print(os.path.isdir(path)) # 是否是目录
# 环境变量
print(os.environ.get('HOME'))
os.environ['MY_VAR'] = 'value'
# 执行系统命令
os.system('ls -la')
result = os.popen('ls -la').read()
sys 模块 - 系统相关
import sys
# Python 版本
print(sys.version)
print(sys.version_info)
# 命令行参数
print(sys.argv) # ['script.py', 'arg1', 'arg2']
# 标准输入输出
sys.stdout.write("Hello\n")
sys.stderr.write("Error\n")
# 退出程序
sys.exit(0) # 正常退出
sys.exit(1) # 异常退出
# 模块搜索路径
print(sys.path)
# 已加载的模块
print(list(sys.modules.keys())[:10])
datetime 模块 - 日期时间
from datetime import datetime, date, time, timedelta
# 当前时间
now = datetime.now()
print(now) # 2024-01-15 10:30:00.123456
print(now.strftime("%Y-%m-%d %H:%M:%S"))
# 创建日期时间
dt = datetime(2024, 1, 15, 10, 30, 0)
d = date(2024, 1, 15)
t = time(10, 30, 0)
# 日期运算
tomorrow = now + timedelta(days=1)
yesterday = now - timedelta(days=1)
next_hour = now + timedelta(hours=1)
# 解析字符串
date_str = "2024-01-15 10:30:00"
parsed = datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S")
# 时间差
diff = tomorrow - now
print(diff.days) # 1
print(diff.seconds) # 0
json 模块 - JSON 处理
import json
# Python 对象转 JSON
data = {
"name": "张三",
"age": 25,
"city": "北京",
"hobbies": ["读书", "游泳"]
}
json_str = json.dumps(data, ensure_ascii=False, indent=2)
print(json_str)
# JSON 转 Python 对象
parsed = json.loads(json_str)
print(parsed["name"])
# 读写 JSON 文件
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_data = json.load(f)
re 模块 - 正则表达式
import re
# 匹配
text = "我的电话是 138-1234-5678"
pattern = r"\d{3}-\d{4}-\d{4}"
match = re.search(pattern, text)
if match:
print(match.group()) # 138-1234-5678
# 查找所有
emails = re.findall(r'\w+@\w+\.\w+', "联系: test@example.com 或 admin@test.org")
print(emails) # ['test@example.com', 'admin@test.org']
# 替换
text = "颜色: red, green, blue"
result = re.sub(r'red|green|blue', 'COLOR', text)
print(result) # 颜色: COLOR, COLOR, COLOR
# 分割
parts = re.split(r'[,\s]+', "apple,banana orange grape")
print(parts) # ['apple', 'banana', 'orange', 'grape']
# 编译正则(重复使用时更高效)
pattern = re.compile(r'\d+')
numbers = pattern.findall("我有 3 个苹果和 5 个橙子")
print(numbers) # ['3', '5']
collections 模块 - 容器数据类型
from collections import Counter, defaultdict, OrderedDict, deque, namedtuple
# Counter - 计数器
words = ["apple", "banana", "apple", "orange", "banana", "apple"]
counter = Counter(words)
print(counter) # Counter({'apple': 3, 'banana': 2, 'orange': 1})
print(counter.most_common(2)) # [('apple', 3), ('banana', 2)]
# defaultdict - 默认字典
dd = defaultdict(list)
dd['fruits'].append('apple')
dd['fruits'].append('banana')
print(dd) # defaultdict(<class 'list'>, {'fruits': ['apple', 'banana']})
# OrderedDict - 有序字典(Python 3.7+ dict 已有序)
od = OrderedDict()
od['first'] = 1
od['second'] = 2
od['third'] = 3
# deque - 双端队列
dq = deque([1, 2, 3])
dq.appendleft(0) # [0, 1, 2, 3]
dq.append(4) # [0, 1, 2, 3, 4]
dq.popleft() # [1, 2, 3, 4]
# namedtuple - 命名元组
Point = namedtuple('Point', ['x', 'y'])
p = Point(10, 20)
print(p.x, p.y) # 10 20
itertools 模块 - 迭代器工具
from itertools import count, cycle, repeat, chain, combinations, permutations
# 无限迭代器
for i in count(10, 2): # 从10开始,步长2
if i > 20:
break
print(i, end=" ") # 10 12 14 16 18 20
# 循环迭代器
colors = cycle(['red', 'green', 'blue'])
for _ in range(5):
print(next(colors), end=" ") # red green blue red green
# 组合和排列
items = [1, 2, 3]
print(list(combinations(items, 2))) # [(1,2), (1,3), (2,3)]
print(list(permutations(items, 2))) # [(1,2), (1,3), (2,1), (2,3), (3,1), (3,2)]
# 链式迭代
combined = chain([1, 2], [3, 4], [5, 6])
print(list(combined)) # [1, 2, 3, 4, 5, 6]
functools 模块 - 高阶函数
from functools import lru_cache, wraps, partial, reduce
# 缓存装饰器
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(100)) # 快速计算
# 偏函数
def power(base, exponent):
return base ** exponent
square = partial(power, exponent=2)
cube = partial(power, exponent=3)
print(square(5)) # 25
print(cube(5)) # 125
# reduce
from functools import reduce
numbers = [1, 2, 3, 4, 5]
product = reduce(lambda x, y: x * y, numbers)
print(product) # 120
第三方包管理
pip 基本用法
# 安装包
pip install requests
pip install numpy==1.21.0 # 指定版本
pip install "django>=3.0,<4.0" # 版本范围
# 卸载包
pip uninstall requests
# 查看已安装的包
pip list
pip freeze
# 导出依赖
pip freeze > requirements.txt
# 从文件安装
pip install -r requirements.txt
# 升级包
pip install --upgrade requests
# 查看包信息
pip show requests
requirements.txt
# requirements.txt
requests==2.28.0
numpy>=1.21.0
pandas~=1.4.0
flask>=2.0,<3.0
# 安装所有依赖
pip install -r requirements.txt
pyproject.toml(现代方式)
# pyproject.toml
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.backends._legacy:_Backend"
[project]
name = "my-package"
version = "1.0.0"
description = "我的 Python 包"
authors = [
{name = "Your Name", email = "your@email.com"}
]
dependencies = [
"requests>=2.28.0",
"numpy>=1.21.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0",
"black>=22.0",
]
Poetry(依赖管理工具)
# 初始化项目
poetry init
# 添加依赖
poetry add requests
poetry add numpy --group dev
# 安装依赖
poetry install
# 运行命令
poetry run python main.py
# 构建包
poetry build
poetry publish
虚拟环境
为什么需要虚拟环境?
- 隔离不同项目的依赖
- 避免版本冲突
- 便于部署和分享
venv(内置)
# 创建虚拟环境
python -m venv myenv
# 激活虚拟环境
# macOS/Linux
source myenv/bin/activate
# Windows
myenv\Scripts\activate
# 退出虚拟环境
deactivate
# 在虚拟环境中安装包
pip install requests
pip freeze > requirements.txt
virtualenv(第三方)
# 安装
pip install virtualenv
# 创建
virtualenv myenv
# 指定 Python 版本
virtualenv -p python3.9 myenv
conda
# 创建环境
conda create -n myenv python=3.9
# 激活
conda activate myenv
# 安装包
conda install numpy
conda install -c conda-forge requests
# 导出环境
conda env export > environment.yml
# 从文件创建
conda env create -f environment.yml
虚拟环境最佳实践
# 1. 每个项目使用独立的虚拟环境
project1/
├── venv/
├── src/
└── requirements.txt
project2/
├── venv/
├── src/
└── requirements.txt
# 2. 将虚拟环境目录加入 .gitignore
echo "venv/" >> .gitignore
echo ".env/" >> .gitignore
# 3. 始终记录依赖
pip freeze > requirements.txt
模块最佳实践
1. 模块化设计原则
# ❌ 不推荐:一个大文件做所有事
# app.py (1000+ 行)
# ✅ 推荐:拆分成多个模块
myapp/
├── __init__.py
├── models.py # 数据模型
├── views.py # 视图逻辑
├── services.py # 业务逻辑
├── utils.py # 工具函数
└── config.py # 配置
2. 清晰的导入组织
# ✅ 推荐的导入顺序
# 1. 标准库
import os
import sys
from datetime import datetime
# 2. 第三方库
import requests
import numpy as np
# 3. 本地应用/库
from myapp.models import User
from myapp.utils import helper
# 每组之间空一行
3. 避免循环导入
# ❌ 循环导入
# module_a.py
from module_b import func_b # 导入 module_b
def func_a():
return "A"
# module_b.py
from module_a import func_a # 导入 module_a(循环!)
def func_b():
return "B"
# ✅ 解决方案1:延迟导入
# module_b.py
def func_b():
from module_a import func_a # 在函数内导入
return func_a()
# ✅ 解决方案2:重构代码,提取共同依赖
# common.py
def shared_function():
pass
# module_a.py
from common import shared_function
# module_b.py
from common import shared_function
4. 使用 __all__ 控制公开接口
# my_module.py
__all__ = ['public_func1', 'public_func2']
def public_func1():
"""公开函数"""
pass
def public_func2():
"""公开函数"""
pass
def _private_func():
"""私有函数,不会被 import * 导入"""
pass
5. 编写模块文档
"""
模块名称:数据处理工具
功能描述:
提供常用的数据处理和转换功能。
主要功能:
- 数据清洗
- 格式转换
- 统计分析
使用示例:
>>> from data_utils import clean_data
>>> clean_data([1, None, 3])
[1, 3]
作者:Your Name
版本:1.0.0
"""
__author__ = "Your Name"
__version__ = "1.0.0"
6. 懒加载
# 大型模块可以懒加载以提高启动速度
class LazyModule:
def __init__(self, module_name):
self._module_name = module_name
self._module = None
def __getattr__(self, name):
if self._module is None:
self._module = __import__(self._module_name)
return getattr(self._module, name)
# 使用
numpy = LazyModule('numpy')
# 只有在首次使用时才真正导入
array = numpy.array([1, 2, 3])
综合实战
实战1: 插件系统
"""
插件系统示例
展示如何使用动态导入实现可扩展的插件架构
"""
import os
import importlib
import inspect
from typing import Dict, List, Callable
class PluginManager:
"""插件管理器"""
def __init__(self, plugin_dir: str = "plugins"):
self.plugin_dir = plugin_dir
self.plugins: Dict[str, object] = {}
self.hooks: Dict[str, List[Callable]] = {}
def discover_plugins(self):
"""自动发现并加载插件"""
if not os.path.exists(self.plugin_dir):
print(f"插件目录不存在: {self.plugin_dir}")
return
for filename in os.listdir(self.plugin_dir):
if filename.endswith('.py') and not filename.startswith('_'):
module_name = filename[:-3]
self.load_plugin(module_name)
def load_plugin(self, module_name: str):
"""加载单个插件"""
try:
# 动态导入模块
module_path = f"{self.plugin_dir}.{module_name}"
module = importlib.import_module(module_path)
# 查找插件类
for name, obj in inspect.getmembers(module):
if (inspect.isclass(obj) and
hasattr(obj, 'name') and
hasattr(obj, 'execute')):
plugin = obj()
self.plugins[plugin.name] = plugin
# 注册钩子
if hasattr(plugin, 'hooks'):
for hook_name in plugin.hooks:
if hook_name not in self.hooks:
self.hooks[hook_name] = []
self.hooks[hook_name].append(plugin.execute)
print(f"✓ 插件加载成功: {plugin.name}")
except Exception as e:
print(f"✗ 插件加载失败: {module_name}, 错误: {e}")
def execute_hook(self, hook_name: str, *args, **kwargs):
"""执行钩子"""
if hook_name not in self.hooks:
return []
results = []
for callback in self.hooks[hook_name]:
try:
result = callback(*args, **kwargs)
results.append(result)
except Exception as e:
print(f"钩子执行失败: {e}")
return results
def list_plugins(self) -> List[str]:
"""列出所有已加载的插件"""
return list(self.plugins.keys())
# 插件基类
class Plugin:
"""插件基类"""
name = "base_plugin"
hooks = []
def execute(self, *args, **kwargs):
raise NotImplementedError
# 示例插件目录结构
"""
plugins/
├── __init__.py
├── greeting_plugin.py
└── logging_plugin.py
"""
# plugins/greeting_plugin.py
"""
from plugin_system import Plugin
class GreetingPlugin(Plugin):
name = "greeting"
hooks = ['on_start', 'on_stop']
def execute(self, *args, **kwargs):
event = kwargs.get('event', '')
if event == 'on_start':
return "欢迎使用系统!"
elif event == 'on_stop':
return "再见!"
return ""
"""
# 使用示例
def main():
manager = PluginManager("plugins")
# 自动发现插件
manager.discover_plugins()
# 列出插件
print(f"已加载插件: {manager.list_plugins()}")
# 执行钩子
results = manager.execute_hook('on_start', event='on_start')
for result in results:
print(result)
if __name__ == "__main__":
main()
实战2: 配置管理系统
"""
配置管理系统
展示如何组织和管理应用程序配置
"""
import os
import json
import yaml
from pathlib import Path
from typing import Any, Dict, Optional
class ConfigError(Exception):
"""配置错误"""
pass
class Config:
"""配置管理类"""
def __init__(self, config_dir: str = "config"):
self.config_dir = Path(config_dir)
self._config: Dict[str, Any] = {}
self._loaded_files: list = []
def load(self, filename: str, required: bool = True):
"""加载配置文件"""
filepath = self.config_dir / filename
if not filepath.exists():
if required:
raise ConfigError(f"配置文件不存在: {filepath}")
return
# 根据扩展名选择加载器
if filepath.suffix == '.json':
self._load_json(filepath)
elif filepath.suffix in ['.yaml', '.yml']:
self._load_yaml(filepath)
elif filepath.suffix == '.py':
self._load_python(filepath)
else:
raise ConfigError(f"不支持的配置格式: {filepath.suffix}")
self._loaded_files.append(str(filepath))
def _load_json(self, filepath: Path):
"""加载 JSON 配置"""
with open(filepath, 'r', encoding='utf-8') as f:
data = json.load(f)
self._merge_config(data)
def _load_yaml(self, filepath: Path):
"""加载 YAML 配置"""
try:
import yaml
except ImportError:
raise ConfigError("需要安装 PyYAML: pip install pyyaml")
with open(filepath, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f)
if data:
self._merge_config(data)
def _load_python(self, filepath: Path):
"""加载 Python 配置"""
import importlib.util
spec = importlib.util.spec_from_file_location("config_module", filepath)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
# 提取大写的变量作为配置
data = {
key: value
for key, value in vars(module).items()
if key.isupper() and not key.startswith('_')
}
self._merge_config(data)
def _merge_config(self, new_config: Dict[str, Any]):
"""合并配置(新配置覆盖旧配置)"""
self._deep_merge(self._config, new_config)
def _deep_merge(self, base: dict, override: dict):
"""深度合并字典"""
for key, value in override.items():
if (key in base and
isinstance(base[key], dict) and
isinstance(value, dict)):
self._deep_merge(base[key], value)
else:
base[key] = value
def get(self, key: str, default: Any = None) -> Any:
"""获取配置值"""
keys = key.split('.')
value = self._config
for k in keys:
if isinstance(value, dict):
value = value.get(k)
if value is None:
return default
else:
return default
return value
def set(self, key: str, value: Any):
"""设置配置值"""
keys = key.split('.')
config = self._config
for k in keys[:-1]:
if k not in config:
config[k] = {}
config = config[k]
config[keys[-1]] = value
def load_environment(self, prefix: str = "APP_"):
"""从环境变量加载配置"""
env_config = {}
for key, value in os.environ.items():
if key.startswith(prefix):
# 转换环境变量名为配置键
config_key = key[len(prefix):].lower().replace('_', '.')
# 尝试转换类型
typed_value = self._parse_value(value)
env_config[config_key] = typed_value
self._merge_config(env_config)
def _parse_value(self, value: str) -> Any:
"""解析字符串值为适当类型"""
# 布尔值
if value.lower() in ('true', 'yes', '1'):
return True
if value.lower() in ('false', 'no', '0'):
return False
# 整数
try:
return int(value)
except ValueError:
pass
# 浮点数
try:
return float(value)
except ValueError:
pass
# JSON 数组或对象
if value.startswith('[') or value.startswith('{'):
try:
return json.loads(value)
except json.JSONDecodeError:
pass
# 默认返回字符串
return value
def validate(self, schema: Dict[str, type]):
"""验证配置"""
errors = []
for key, expected_type in schema.items():
value = self.get(key)
if value is None:
errors.append(f"缺少必需配置: {key}")
elif not isinstance(value, expected_type):
errors.append(
f"配置 '{key}' 类型错误: "
f"期望 {expected_type.__name__}, "
f"实际 {type(value).__name__}"
)
if errors:
raise ConfigError("配置验证失败:\n" + "\n".join(errors))
def save(self, filepath: str):
"""保存配置到文件"""
path = Path(filepath)
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, 'w', encoding='utf-8') as f:
json.dump(self._config, f, indent=2, ensure_ascii=False)
def __getitem__(self, key: str) -> Any:
return self.get(key)
def __setitem__(self, key: str, value: Any):
self.set(key, value)
def __repr__(self):
return f"Config({self._config})"
# 使用示例
def main():
# 创建配置管理器
config = Config("config")
# 加载配置文件(按优先级)
config.load("default.yaml", required=False)
config.load("production.yaml", required=False)
# 加载环境变量(最高优先级)
config.load_environment("APP_")
# 获取配置
db_host = config.get("database.host", "localhost")
db_port = config.get("database.port", 5432)
debug = config.get("app.debug", False)
print(f"数据库: {db_host}:{db_port}")
print(f"调试模式: {debug}")
# 验证配置
schema = {
"database.host": str,
"database.port": int,
"app.debug": bool,
}
try:
config.validate(schema)
print("✓ 配置验证通过")
except ConfigError as e:
print(f"✗ 配置验证失败: {e}")
if __name__ == "__main__":
main()
实战3: 日志系统封装
"""
日志系统封装
展示如何创建统一的日志模块
"""
import os
import sys
import logging
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
from pathlib import Path
from typing import Optional
class LoggerFactory:
"""日志工厂"""
_loggers = {}
@classmethod
def get_logger(
cls,
name: str,
level: int = logging.INFO,
log_dir: str = "logs",
max_bytes: int = 10 * 1024 * 1024, # 10MB
backup_count: int = 5,
console_output: bool = True,
file_output: bool = True,
) -> logging.Logger:
"""获取或创建 logger"""
if name in cls._loggers:
return cls._loggers[name]
logger = logging.getLogger(name)
logger.setLevel(level)
# 避免重复添加 handler
if logger.handlers:
cls._loggers[name] = logger
return logger
# 格式化器
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# 控制台处理器
if console_output:
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(level)
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
# 文件处理器
if file_output:
log_path = Path(log_dir)
log_path.mkdir(parents=True, exist_ok=True)
# 按大小轮转
log_file = log_path / f"{name}.log"
file_handler = RotatingFileHandler(
log_file,
maxBytes=max_bytes,
backupCount=backup_count,
encoding='utf-8'
)
file_handler.setLevel(level)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
# 错误日志单独文件
error_file = log_path / f"{name}_error.log"
error_handler = RotatingFileHandler(
error_file,
maxBytes=max_bytes,
backupCount=backup_count,
encoding='utf-8'
)
error_handler.setLevel(logging.ERROR)
error_handler.setFormatter(formatter)
logger.addHandler(error_handler)
cls._loggers[name] = logger
return logger
@classmethod
def cleanup(cls):
"""清理所有 logger"""
for logger in cls._loggers.values():
for handler in logger.handlers[:]:
handler.close()
logger.removeHandler(handler)
cls._loggers.clear()
# 便捷函数
def get_logger(name: Optional[str] = None, **kwargs) -> logging.Logger:
"""快速获取 logger"""
if name is None:
# 使用调用者的模块名
import inspect
frame = inspect.currentframe().f_back
name = frame.f_globals.get('__name__', 'unknown')
return LoggerFactory.get_logger(name, **kwargs)
# 使用示例
def main():
# 获取 logger
logger = get_logger("myapp", level=logging.DEBUG)
# 记录日志
logger.debug("调试信息")
logger.info("一般信息")
logger.warning("警告信息")
logger.error("错误信息")
try:
result = 1 / 0
except Exception as e:
logger.exception("发生异常") # 自动包含堆栈跟踪
# 不同模块使用不同的 logger
db_logger = get_logger("myapp.database")
db_logger.info("数据库连接成功")
api_logger = get_logger("myapp.api")
api_logger.info("API 请求处理完成")
if __name__ == "__main__":
main()
小结
| 概念 | 说明 | 使用场景 |
|---|---|---|
| 模块 | .py 文件 | 代码组织、复用 |
| 包 | 包含模块的目录 | 大型项目管理 |
| import | 导入模块 | 使用其他模块的功能 |
__init__.py | 包初始化文件 | 定义包接口 |
| 相对导入 | from . import | 包内部模块引用 |
| 虚拟环境 | 隔离依赖 | 多项目管理 |
| pip | 包管理工具 | 安装第三方包 |
__name__ | 模块名称 | 判断是否主程序 |
__all__ | 公开接口 | 控制 import * |
核心要点:
- 模块是代码复用的基本单位
- 包用于组织相关模块
- 合理使用导入方式避免循环依赖
- 使用虚拟环境隔离项目依赖
- 遵循 PEP 8 导入规范
- 使用
__all__明确公开接口 - 编写清晰的模块文档
- 利用内置模块提高效率
掌握模块与包的管理是构建大型 Python 项目的基础!