函数写得好不好,是区分新手和老手的重要标志。这 5 个函数技巧,每个都来自真实项目的实战经验。
1. 用 property 装饰器实现属性访问控制
看起来没问题,但其实有更好的方式:
class Circle: def __init__(self, radius): self._radius = radius def get_radius(self): return self._radius def set_radius(self, value): if value < 0: raise ValueError self._radius = value
换个思路,问题迎刃而解:
import mathclass Circle: def __init__(self, radius): self.radius = radius # 触发 setter @property def radius(self): return self._radius @radius.setter def radius(self, value): if value < 0: raise ValueError('Radius must be non-negative') self._radius = value @property def area(self): return math.pi * self._radius ** 2c = Circle(5)print(c.area) # 78.54 (像属性一样访问)
property 装饰器让方法像属性一样访问,在赋值时自动校验,在读取时计算派生值。Pythonic 的方式不是写 get/set 方法,而是用 property。
2. 函数参数中的 / 和 * 分隔符
搜一下 Stack Overflow,排第一的答案通常长这样:
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 只能按位置传# greet('Alice', 'Hi') # TypeError! greeting 只能用关键字
/ 之前的参数只能按位置传,* 之后的只能用关键字传。这样可以防止调用者依赖参数名(方便以后重命名),同时强制某些参数必须显式命名。
3. 用 lambda 创建匿名函数
看起来没问题,但其实有更好的方式:
def square(x): return x ** 2numbers = [1, 2, 3, 4, 5]result = list(map(square, numbers))
换个思路,问题迎刃而解:
numbers = [1, 2, 3, 4, 5]result = list(map(lambda x: x ** 2, numbers))print(result) # [1, 4, 9, 16, 25]# 更 Pythonic: 用列表推导式result = [x ** 2 for x in numbers]
lambda 适合简单的一行函数,常与 map/filter/sorted 配合。但如果逻辑复杂或需要复用,应该定义具名函数。列表推导式通常比 map+lambda 更清晰。
4. 用 any/all 简化条件判断
包括我自己,一开始也是这么干的:
numbers = [2, 4, 6, 8, 10]all_even = Truefor n in numbers: if n % 2 != 0: all_even = False break
换个思路,问题迎刃而解:
numbers = [2, 4, 6, 8, 10]print(all(n % 2 == 0 for n in numbers)) # True - 全部为偶数print(any(n > 8 for n in numbers)) # True - 存在大于8的print(any(n < 0 for n in numbers)) # False - 不存在负数
all() 检查所有元素是否为真,any() 检查是否存在真元素。配合生成器表达式,一行代码替代循环+标志变量,而且支持短路求值。
5. 用 *args 和 **kwargs 接收可变参数
刚学 Python 的时候我这么写过:
def log(level, msg, user=None, action=None): # 每增加一个字段就要改函数签名 print(f'[{level}] {msg} user={user} action={action}')
其实标准库早就有答案了:
def log(level, msg, **context): details = ' '.join(f'{k}={v}' for k, v in context.items()) print(f'[{level}] {msg}{details}')log('INFO', 'User login', user='Alice', ip='1.2.3.4')
*args 收集位置参数为元组,**kwargs 收集关键字参数为字典。让函数签名更灵活,新增字段无需修改函数定义,常用于日志、装饰器等场景。
速查表
| 场景 | 别这样写 | 试试这样 |
|---|
| 属性访问控制 | get_radius() / set_radius() | @property 装饰器 |
| 强制位置/关键字参数 | 参数顺序靠约定 | / 和 * 分隔符 |
| 匿名函数 | def square(x): return x**2 | lambda x: x**2 |
| 全部/任一判断 | for 循环 + 标志变量 | all() / any() |
| 可变参数 | 逐个写 param=None | **kwargs |
以上就是函数技巧的 5 个实用技巧,每个都是我在实际项目中踩过坑才总结出来的。挑一个今天就用上,慢慢积累,你的代码质量会肉眼可见地提升。
评论区聊聊:这 5 个技巧里哪个你之前不知道?
下期预告:内置模块专场
关于作者
写了 10 年 Python,赶上了机器学习的热潮,又撞上了大模型的浪头,每次以为学明白了,行业又变了一次。把自己踩过的坑、走过的弯路、看过的热闹,说给还在路上的人听。
关注「鲁叶的Python」,一起穿越迷茫期。