十年写下来,我发现函数写得好不好,真的能看出一个人的编程段位。
但别被 "段位" 这个词吓到——今天这 5 个技巧不难,关键是养成习惯。
1. 用 operator 模块替代 lambda
那次代码评审,同事指着我的lambda表达式说,这玩意儿能用operator.replace,性能翻倍。
我当场愣住,原来Python还藏着这种“作弊码”。
新手常这么写 :
from functools import reducenumbers = [1, 2, 3, 4, 5]product = reduce(lambda x, y: x * y, numbers)students = [{'name': 'Alice', 'score': 85}]sorted_students = sorted(students, key=lambda s: s['score'])老司机推荐用 operator 提速 :
from functools import reducefrom operator import mul, itemgetternumbers = [1, 2, 3, 4, 5]product = reduce(mul, numbers) # 比 lambda 更快print(product) # 120students = [{'name': 'Alice', 'score': 85}]sorted_students = sorted(students, key=itemgetter('score'))operator 模块提供函数化的运算符:mul(乘)、add(加)、itemgetter(取值)等。比 lambda 更快(C 实现),语义也更清晰。
2. 用 singledispatch 实现函数重载
有次为了处理多种数据类型的逻辑,写了满屏的 if isinstance 判断,直到看到同事用装饰器优雅地实现了“类型分发”,才惊觉自己写代码像在用蛮力搬砖。
大家容易陷入的写法 :
def process(data): if isinstance(data, str): return data.upper() elif isinstance(data, list): return [x * 2 for x in data] elif isinstance(data, dict): return {k: v.upper() for k, v in data.items()}更 Pythonic 的解耦姿势 :
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]@process.register(dict)def _(data): return {k: v.upper() for k, v in data.items()}singledispatch 实现基于第一个参数类型的函数重载,比一连串 isinstance 更优雅可扩展。新类型只需注册新实现,不修改原函数。
3. 用 reduce 实现累积计算
有次在GitHub上刷到一个开源项目,看到作者用reduce一行代码完成了我原本需要写循环才能实现的累积计算,当场就惊了——原来Python的函数式编程还能这么玩!
很多人习惯这样写 :
numbers = [1, 2, 3, 4, 5]total = 0for n in numbers: total += nprint(total)
更优雅的函数式写法 :
from functools import reducefrom operator import mulnumbers = [1, 2, 3, 4, 5]# 求积product = reduce(mul, numbers)print(product) # 120# 求最大公约数from math import gcdfrom functools import reducenums = [12, 18, 24]result = reduce(gcd, nums)print(result) # 6
reduce 将二元函数逐步应用于序列,实现左折叠。求和用 sum(),求积可以用 reduce(mul)。注意 reduce 在 Python 3 中移到了 functools。
4. 用 contextmanager 自定义上下文管理器
这个坑我踩过,说出来不怕你笑话,当时为了写个计时器类,光 __enter__ 和 __exit__ 的样板代码就敲得手酸,debug 了半小时才发现是缩进出了问题。
很多人会这样写:
import timeclass Timer: def __enter__(self): self.start = time.perf_counter() return self def __exit__(self, *args): self.elapsed = time.perf_counter() - self.start print(f'耗时: {self.elapsed:.4f}s')老司机推荐用装饰器“降维打击”:
import timefrom contextlib import contextmanager@contextmanagerdef timer(label='Block'): start = time.perf_counter() yield elapsed = time.perf_counter() - start print(f'{label} 耗时: {elapsed:.4f}s')with timer('排序'): sorted(range(1000000))contextmanager 装饰器让你用生成器函数创建上下文管理器,比写 __enter__/__exit__ 类简洁得多。yield 前是进入逻辑,yield 后是退出逻辑。
5. 函数参数中的 / 和 * 分隔符
学生时代要是早知道这个,我的课程作业至少能快一倍。
很多人会这样写:
def greet(name, greeting='Hello'): # 调用者可能写 greet(greeting='Hi', name='Bob') # 但你想强制 name 只能按位置传 print(f'{greeting}, {name}!')老司机推荐用参数分隔符精准控制:
def greet(name, /, *, greeting='Hello'): print(f'{greeting}, {name}!')greet('Alice') # OKgreet('Bob', greeting='Hi') # OK# greet(name='Alice') # TypeError! name 只能按位置传/ 之前的参数只能按位置传,* 之后的只能用关键字传。这样可以防止调用者依赖参数名(方便以后重命名),同时强制某些参数必须显式命名。
这 5 招不算高深,但都是实打实用了十年的东西,希望你少走我走过的弯路。
觉得有用就转给你身边正在学 Python 的朋友。
下期预告:内置模块专场
关于作者
写了 10 年 Python,赶上了机器学习的热潮,又撞上了大模型的浪头,每次以为学明白了,行业又变了一次。把自己踩过的坑、走过的弯路、看过的热闹,说给还在路上的人听。
关注「鲁叶的Python」,一起穿越迷茫期。