技术备忘 Python

前言

Python 写起来很舒服,但有些坑和特性如果不留意,容易绕远路。这篇不是什么系统的教程,就是把日常写码过程中遇到的一些值得记下来的东西写下来。

装饰器

基础装饰器

python
from functools import wraps
def log_time(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f'{func.__name__} took {time.time() - start:.3f}s')
return result
return wrapper
@log_time
def slow_function():
time.sleep(1)
装饰器效果示例
@staticmethod静态方法,不接收 selfclassmethod 的替代
@classmethod类方法,接收 cls工厂方法
@property方法变属性访问obj.attr 替代 obj.attr()
@functools.cache缓存函数返回值递归加速

@wraps 的作用是把被装饰函数的 __name____doc__ 等属性复制到包装函数上。不加的话调试时会看到包装函数的名字而非原函数名。

带参数的装饰器

python
def retry(max_attempts=3):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for i in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if i == max_attempts - 1:
raise
print(f'重试第 {i+1} 次')
return None
return wrapper
return decorator
@retry(max_attempts=5)
def unstable_api_call():
...

上下文管理器

上下文管理器是 Python 里处理资源的好工具,最常见的用法是 with open(...) as f:

自定义上下文管理器

python
# 方式一:__enter__ / __exit__
class Timer:
def __enter__(self):
self.start = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.elapsed = time.time() - self.start
print(f'耗时: {self.elapsed:.3f}s')
with Timer() as t:
do_something()
# 方式二:contextmanager 装饰器
from contextlib import contextmanager
@contextmanager
def timer():
start = time.time()
yield
print(f'耗时: {time.time() - start:.3f}s')
with timer():
do_something()
方式适用场景
__enter__ / __exit__需要类的状态管理(如数据库连接)
@contextmanager简单的资源管理,写起来更省事

contextlib 常用工具

python
from contextlib import suppress, ExitStack
# 忽略特定异常(比 try/except/pass 干净)
with suppress(FileNotFoundError):
os.remove('temp.txt')
# 同时管理多个上下文
with ExitStack() as stack:
files = [stack.enter_context(open(f)) for f in ['a.txt', 'b.txt']]
# 所有文件会在退出 with 块时自动关闭
NOTE

contextlib.suppresstry: ... except: pass 更清晰,意图明确——不是忘记处理异常,是确定地忽略它。

生成器与迭代器

python
# 生成器函数
def read_large_file(file_path):
with open(file_path) as f:
for line in f:
yield line.strip()
for line in read_large_file('data.csv'):
process(line) # 逐行处理,不会把整个文件读进内存
特性列表 []生成器 ()
内存占用全部在内存随用随生成
可迭代次数多次一次
支持下标
适用场景数据量小、需多次访问数据量大、只遍历一次
python
# yield from:委托给另一个生成器
def flatten(lists):
for sub in lists:
yield from sub
for item in flatten([[1, 2], [3, 4], [5]]):
print(item) # 1 2 3 4 5

常用内置函数

python
# enumerate:同时拿下标和值
for i, v in enumerate(['a', 'b', 'c'], start=1):
print(i, v) # 1 a / 2 b / 3 c
# zip:合并多个可迭代对象
for name, age in zip(['Alice', 'Bob'], [30, 25]):
print(f'{name}: {age}')
# itertools 链式迭代
from itertools import chain, groupby, product
list(chain([1,2], [3,4])) # [1, 2, 3, 4]
函数作用示例
enumerate(iter, start=0)带索引遍历for i, v in enumerate(items)
zip(*iterables)并行遍历for a, b in zip(xs, ys)
map(func, iter)映射map(str.upper, ['a', 'b'])
filter(func, iter)过滤filter(None, [0, 1, '', 'a'])
sorted(iter, key=)排序sorted(dict.items(), key=lambda x: x[1])
any(iter) / all(iter)条件判断all(x > 0 for x in nums)

数据类(dataclass)

python
from dataclasses import dataclass, field
@dataclass
class User:
name: str
age: int = 0
tags: list[str] = field(default_factory=list)
# 自动生成 __init__、__repr__、__eq__ 等
u = User('Alice', 30)
print(u) # User(name='Alice', age=30)
print(u == User('Alice', 30)) # True
dataclass vs 普通 classdataclass普通 class
需要写的代码量多(initrepr…)
可读性字段一目了然得看 init 参数
可变性默认可变(可 frozen=True)灵活
适用场景数据传输对象(DTO)复杂的业务对象
NOTE

Python 3.10+ 的 dataclass 支持 slots=True,可以省内存、提速属性访问。3.7+ 的基本用法已经很好用,别再手写 init 了除非有特殊逻辑

类型提示(Type Hints)

python
from typing import Optional, List, Dict, Tuple
def greet(name: str) -> str:
return f'Hello, {name}'
# 可选类型
def find_user(id: int) -> Optional[dict]:
...
# 3.10+ 写法更简洁
def process(items: list[str]) -> dict[str, int]:
return {item: len(item) for item in items}
# Union 简化
# 旧:Union[int, str] → 新:int | str
# 旧:Optional[str] → 新:str | None

类型提示在写库和团队协作时特别有用。配合 mypy 或 pyright 可以在 CI 中做类型检查。

虚拟环境与依赖管理

工具特点什么时候用
venv内置,无额外依赖基础项目
poetry依赖解析 + 打包发布Python 包开发
pipenv自动管理虚拟环境和依赖简单项目
uv极快,兼容 pip/requirements.txt想尝鲜 替代 pip
bash
# venv
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
ini
# requirements.txt
requests==2.31.0
flask>=2.3,<3.0

常见陷阱

默认参数的可变对象

python
# ❌ 大坑
def add_item(item, lst=[]):
lst.append(item)
return lst
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2] ← 不是 [2]!
# ✅ 正确
def add_item(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst

默认参数在函数定义时求值,而不是每次调用时。可变默认参数会累积状态。

闭包延迟绑定

python
# ❌ 你以为输出什么?
funcs = []
for i in range(3):
funcs.append(lambda: i)
for f in funcs:
print(f()) # 2 2 2 ← 不是 0 1 2
# ✅ 正确:默认参数绑定当前值
funcs = []
for i in range(3):
funcs.append(lambda i=i: i)

for 循环的 else 子句

python
# else 在循环没有被 break 时执行
for n in range(2, 10):
for x in range(2, n):
if n % x == 0:
break
else:
print(n, '是质数')

知道有这个东西就好,生产代码里写这个会被同事打。不如拆成函数加 return 来得直观。

浅拷贝 vs 深拷贝

python
import copy
original = {'nested': {'a': 1}}
shallow = copy.copy(original)
deep = copy.deepcopy(original)
shallow['nested']['a'] = 99
print(original['nested']['a']) # 99 ← 被改了
deep['nested']['a'] = 42
print(original['nested']['a']) # 99 ← 没影响
复制方式嵌套对象使用场景
= 赋值完全共享引用不复制
copy.copy()共享内部引用一层结构
copy.deepcopy()完全独立多层嵌套 ❗可能有性能开销

字符串处理

python
# f-string 格式化(3.6+)
name = 'Alice'
age = 30
print(f'{name} is {age} years old') # Alice is 30 years old
print(f'{name:>10}') # 右对齐: Alice
print(f'{age:04d}') # 补零:0030
# 3.8+ 自调试表达式
print(f'{name=}, {age=}') # name='Alice', age=30
# 3.12+ 支持引号复用
print(f'{name} is {age:,}') # 30 → 30(数字千分位暂不支持)

文件与路径

python
from pathlib import Path
# 告别 os.path.join
data_dir = Path('data')
config = data_dir / 'config.json'
# 读文件(自动处理编码)
content = Path('data.txt').read_text(encoding='utf-8')
# 写文件
Path('output.txt').write_text('hello')
# 遍历目录
for py_file in Path('src').glob('**/*.py'):
print(py_file)
# 确保目录存在
Path('output/logs').mkdir(parents=True, exist_ok=True)
NOTE

pathlib.Path 是 Python 3.4+ 内置的路径处理库。别再 os.path.join 拼字符串了Path 的可读性和跨平台兼容性都好得多。

日志配置

python
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s | %(levelname)-8s | %(name)s | %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
)
logger = logging.getLogger(__name__)
logger.info('应用启动')
logger.error('出错了', exc_info=True)
# 按模块区分日志级别
logging.getLogger('httpx').setLevel(logging.WARNING)
logging.getLogger('urllib3').setLevel(logging.WARNING)
日志级别数值用途
DEBUG10调试信息
INFO20正常流程记录
WARNING30需要注意但不影响运行
ERROR40出错但程序还能继续
CRITICAL50严重错误,程序可能崩溃
NOTE

print() 作为日志的输出方式只在调试阶段勉强可用。logging 模块自带时间戳、调用位置、级别过滤,线上查问题全靠它。

常用标准库

模块用途常用函数/类
datetime日期时间datetime.now()timedelta
jsonJSON 处理dumpsloadsensure_ascii=False
re正则表达式searchsubfindall
collections扩展容器defaultdictCounterdeque
itertools迭代工具chaingroupbyproductbatched(3.12+)
functools函数工具lru_cachepartialreduce
os / shutil操作系统walkrmtreecopy2
subprocess运行子进程runPopen
argparse命令行参数ArgumentParser
hashlib哈希sha256md5
http.server简易 HTTP 服务python -m http.server 8000

学习路线图

  • 掌握装饰器和 @wraps 的用法
  • 理解上下文管理器的两种写法
  • 区分列表推导式和生成器表达式
  • 熟练使用 pathlib 替代 os.path
  • 用 dataclass 替代手写 init
  • 避免默认参数和闭包延迟绑定的坑
  • 用好类型提示和 mypy
  • 学会配置 logging 替代 print

总结

知识点要点
装饰器@wraps 保留元信息、带参数的装饰器包装两层
上下文管理器__enter__/__exit__@contextmanager
生成器节省内存、yield from 委托、只能迭代一次
dataclass省去样板代码、field(default_factory=)
类型提示增加可读性、list[str] 替代 List[str](3.9+)
常见坑可变默认参数、闭包延迟绑定、浅拷贝共享引用
pathlibPath / "file" 替代 os.path.join
logging替代 print,级别过滤 + 时间戳 + exc_info

参考来源