前言
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_timedef slow_function(): time.sleep(1)| 装饰器 | 效果 | 示例 |
|---|---|---|
@staticmethod | 静态方法,不接收 self | classmethod 的替代 |
@classmethod | 类方法,接收 cls | 工厂方法 |
@property | 方法变属性访问 | obj.attr 替代 obj.attr() |
@functools.cache | 缓存函数返回值 | 递归加速 |
@wraps 的作用是把被装饰函数的 __name__、__doc__ 等属性复制到包装函数上。不加的话调试时会看到包装函数的名字而非原函数名。
带参数的装饰器
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:。
自定义上下文管理器
# 方式一:__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
@contextmanagerdef timer(): start = time.time() yield print(f'耗时: {time.time() - start:.3f}s')
with timer(): do_something()| 方式 | 适用场景 |
|---|---|
__enter__ / __exit__ | 需要类的状态管理(如数据库连接) |
@contextmanager | 简单的资源管理,写起来更省事 |
contextlib 常用工具
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.suppress 比 try: ... except: pass 更清晰,意图明确——不是忘记处理异常,是确定地忽略它。
生成器与迭代器
# 生成器函数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) # 逐行处理,不会把整个文件读进内存| 特性 | 列表 [] | 生成器 () |
|---|---|---|
| 内存占用 | 全部在内存 | 随用随生成 |
| 可迭代次数 | 多次 | 一次 |
| 支持下标 | ✅ | ❌ |
| 适用场景 | 数据量小、需多次访问 | 数据量大、只遍历一次 |
# 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常用内置函数
# 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)
from dataclasses import dataclass, field
@dataclassclass 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 普通 class | dataclass | 普通 class |
|---|---|---|
| 需要写的代码量 | 少 | 多(init、repr…) |
| 可读性 | 字段一目了然 | 得看 init 参数 |
| 可变性 | 默认可变(可 frozen=True) | 灵活 |
| 适用场景 | 数据传输对象(DTO) | 复杂的业务对象 |
NOTE
Python 3.10+ 的 dataclass 支持 slots=True,可以省内存、提速属性访问。3.7+ 的基本用法已经很好用,别再手写 init 了除非有特殊逻辑。
类型提示(Type Hints)
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 |
# venvpython -m venv .venvsource .venv/bin/activatepip install -r requirements.txt# requirements.txtrequests==2.31.0flask>=2.3,<3.0常见陷阱
默认参数的可变对象
# ❌ 大坑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默认参数在函数定义时求值,而不是每次调用时。可变默认参数会累积状态。
闭包延迟绑定
# ❌ 你以为输出什么?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 子句
# else 在循环没有被 break 时执行for n in range(2, 10): for x in range(2, n): if n % x == 0: break else: print(n, '是质数')知道有这个东西就好,生产代码里写这个会被同事打。不如拆成函数加 return 来得直观。
浅拷贝 vs 深拷贝
import copy
original = {'nested': {'a': 1}}shallow = copy.copy(original)deep = copy.deepcopy(original)
shallow['nested']['a'] = 99print(original['nested']['a']) # 99 ← 被改了
deep['nested']['a'] = 42print(original['nested']['a']) # 99 ← 没影响| 复制方式 | 嵌套对象 | 使用场景 |
|---|---|---|
= 赋值 | 完全共享引用 | 不复制 |
copy.copy() | 共享内部引用 | 一层结构 |
copy.deepcopy() | 完全独立 | 多层嵌套 ❗可能有性能开销 |
字符串处理
# f-string 格式化(3.6+)name = 'Alice'age = 30print(f'{name} is {age} years old') # Alice is 30 years oldprint(f'{name:>10}') # 右对齐: Aliceprint(f'{age:04d}') # 补零:0030
# 3.8+ 自调试表达式print(f'{name=}, {age=}') # name='Alice', age=30
# 3.12+ 支持引号复用print(f'{name} is {age:,}') # 30 → 30(数字千分位暂不支持)文件与路径
from pathlib import Path
# 告别 os.path.joindata_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 的可读性和跨平台兼容性都好得多。
日志配置
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)| 日志级别 | 数值 | 用途 |
|---|---|---|
DEBUG | 10 | 调试信息 |
INFO | 20 | 正常流程记录 |
WARNING | 30 | 需要注意但不影响运行 |
ERROR | 40 | 出错但程序还能继续 |
CRITICAL | 50 | 严重错误,程序可能崩溃 |
NOTE
只在调试阶段勉强可用。print() 作为日志的输出方式logging 模块自带时间戳、调用位置、级别过滤,线上查问题全靠它。
常用标准库
| 模块 | 用途 | 常用函数/类 |
|---|---|---|
datetime | 日期时间 | datetime.now()、timedelta |
json | JSON 处理 | dumps、loads(ensure_ascii=False) |
re | 正则表达式 | search、sub、findall |
collections | 扩展容器 | defaultdict、Counter、deque |
itertools | 迭代工具 | chain、groupby、product、batched(3.12+) |
functools | 函数工具 | lru_cache、partial、reduce |
os / shutil | 操作系统 | walk、rmtree、copy2 |
subprocess | 运行子进程 | run、Popen |
argparse | 命令行参数 | ArgumentParser |
hashlib | 哈希 | sha256、md5 |
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+) |
| 常见坑 | 可变默认参数、闭包延迟绑定、浅拷贝共享引用 |
| pathlib | Path / "file" 替代 os.path.join |
| logging | 替代 print,级别过滤 + 时间戳 + exc_info |
评论
GitHub 登录后可评论。
评论区会在滚动到这里时自动加载。