写了十年 Python,我发现一个扎心的事实:大部分人学的是语法,高手玩的是内置特性。同样是写函数,新手写 30 行,老手 5 行搞定。不是因为聪明,是知道 Python 已经替你写好了。今天这 5 个技巧,每一个都能让你的代码短一截、快一截、优雅一截。 1. 用 __slots__ 减少实例内存
刚学 Python 的时候我这么写过:
class Point: def __init__(self, x, y): self.x = x self.y = y# 每个实例都有 __dict__,大量实例时浪费内存
Python 内置了更专业的方案:
class Point: __slots__ = ('x', 'y') def __init__(self, x, y): self.x = x self.y = yp = Point(1, 2)print(p.x, p.y)# p.z = 3 # AttributeError! 不能添加额外属性
__slots__ 告诉 Python 只为列出的属性分配空间,省去 __dict__ 的开销。百万级实例时内存可减少 40-50%,访问速度也更快。
2. 用 type hints 标注函数签名
很多教程会教你这么写:
def calculate_area(width, height): return width * height# 调用者不知道参数类型和返回类型
更专业的方案是使用 Python 内置属性:
def calculate_area(width: float, height: float) -> float: """计算矩形面积""" return width * height# 复杂类型from typing import Optionaldef find_user(name: str) -> Optional[dict]: users = {'Alice': {'age': 30}} return users.get(name)
类型注解让代码自文档化,IDE 可以据此提供自动补全和类型检查,配合 mypy 还能在运行前发现类型错误,是现代 Python 项目的标配。
3. 用生成器节省内存
看起来没问题,但其实有更好的方式:
def get_squares(n): result = [] for i in range(n): result.append(i ** 2) return result # 百万级数据占用大量内存for x in get_squares(1000000): pass
老手一般这么写:
def get_squares(n): for i in range(n): yield i ** 2 # 惰性生成,内存恒定for x in get_squares(1000000): pass # 内存占用极小
yield 将函数变为生成器,按需产出值而非一次性构建完整列表。处理百万级数据时,内存从 O(n) 降到 O(1),是大数据处理的基本功。
4. 用 contextmanager 自定义上下文管理器
搜一下 Stack Overflow,排第一的答案通常长这样:
class Timer: def __enter__(self): self.start = time.perf_counter() return self def __exit__(self, *args): self.elapsed = time.perf_counter() - self.start print(f'Elapsed: {self.elapsed:.4f}s')
一行代码就能搞定:
import timefrom contextlib import contextmanager@contextmanagerdef timer(label='Block'): start = time.perf_counter() try: yield finally: elapsed = time.perf_counter() - start print(f'{label}: {elapsed:.4f}s')with timer('Data processing'): time.sleep(0.1)
contextmanager 装饰器让你用生成器函数创建上下文管理器,比写 __enter__/__exit__ 类简洁得多。yield 前是进入逻辑,yield 后是退出逻辑。
5. 用 singledispatch 实现函数重载
看起来没问题,但其实有更好的方式:
def process(data): if isinstance(data, str): return data.upper() elif isinstance(data, list): return [x * 2 for x in data] elif isinstance(data, int): return data ** 2
用对了工具,代码量直接砍半:
from functools import singledispatch@singledispatchdef process(data): raise TypeError(f'Unsupported type: {type(data)}')@process.register(str)def _(data): return data.upper()@process.register(list)def _(data): return [x * 2 for x in data]print(process('hello')) # 'HELLO'print(process([1, 2, 3])) # [2, 4, 6]
singledispatch 实现基于第一个参数类型的函数重载,比一连串 isinstance 更优雅可扩展。新类型只需注册新实现,不修改原函数。
速查表
| 场景 | 别这样写 | 试试这样 |
|---|
| 减少实例内存 | 普通 class 带 __dict__ | __slots__ 限定属性 |
| 函数签名不清晰 | 参数无类型标注 | type hints 标注类型 |
| 大数据占内存 | result.append() 构建列表 | yield 生成器按需产出 |
| 自定义上下文管理器 | 写 __enter__/__exit__ 类 | @contextmanager 一行搞定 |
| 多类型函数重载 | 一连串 isinstance | @singledispatch 注册分发 |
以上就是函数技巧的 5 个实用技巧,每个都是我在实际项目中踩过坑才总结出来的。先收藏起来,勤加练习,一步步进阶为高级工程师。
评论区聊聊:这 5 个技巧里哪个你之前不知道?
下期预告:内置模块专场
关于作者
写了 10 年 Python,赶上了机器学习的热潮,又撞上了大模型的浪头,每次以为学明白了,行业又变了一次。
把自己踩过的坑、走过的弯路、看过的热闹,说给还在路上的人听。
关注「鲁叶的Python」,和我一起穿越迷茫期。