编写高质量Python (第22条) 用数量可变的位置参数给函数设计清晰的参数列表

第 22 条 用数量可变的位置参数给函数设计清晰的参数列表

​ 让函数接收数量可变的位置参数(position argument),可以把函数设计的更加清晰(这些位置通常简称 varargs,或者叫做 star args,因为我们习惯用 *args指代)。例如,假设我们要记录调试信息。如果采用参数数量固定的方案设计,那么函数应该接受一个表示信息的 message 参数和一个 values 列表(这个列表用于存放需要填充到信息的那些值)。

python 复制代码
def log(message, values):
    if not values:
        print(message)
    else:
        values_str = ','.join(str(x) for x in values)
        print(f'{message}: {values_str}')


log('My numbers are', [1, 2])
log('Hi there', [])

>>>
My numbers are: 1,2
Hi there

​ 即便没有值需要填充到信息里面,也必须专门传一个空白的列表上去,这样显得多余,而且让代码看起来比较乱。最好是能允许调用者把第二个参数留空。在 Python 里,可以给最后一个位置参数加上前缀 *,这样调用者就只需要提供不带星号的那些参数,然后可以不再指其他参数,也可以继续指定任意数量的位置参数。函数的主题代码不用改,只修改调用代码即可。

python 复制代码
def log(message, *values):
    if not values:
        print(message)
    else:
        values_str = ','.join(str(x) for x in values)
        print(f'{message}: {values_str}')


log('My numbers are', [1, 2])
log('Hi there')

>>>
My numbers are: 1,2
Hi there

​ 这种写法与拆解数据时用在赋值语句左边带星号的 unpacking 操作非常类似(参见 第13条)。

​ 如果想想把已有序列(例如某列表)里面的元素当成参数传给像 log 这样的参数个数可变的函数,那么可以在传递序列的时采用 * 操作符。这会让 Python 把序列中的元素都当成位置参数传给这个函数。

python 复制代码
favorites = [7, 33, 99]
log('Favorite colors', favorites)

>>>
Favorite colors: 7,33,99

​ 另函数接收数量可变的位置参数,可能导致两个问题。

​ 第一个问题是,程序总是必须先把这些参数转化为一个元组,然后才能把它们当成可选的位置参数传给函数。这意味着,如果调用函数时,把带 * 操作符的生成器传了过去,那么程序必须先把这个生成器的所有元素迭代完(以便形成元组),然后才能继续往下进行(相关知识,参见 第30条)。这个元组包含生成器所给出的每个值,这可能耗费大量内存,甚至会导致程序崩溃。

python 复制代码
def my_generator():
    for i in range(10):
        yield i
        
    
def my_func(*args):
    print(args)
    
    
it = my_generator()
my_func(*it)

>>>
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

​ 接收 *args 参数的函数,适合处理输入值不太多,而且数量可以提前预估的情况。在调用这种函数时,传给 *args 这一部分的应该是许多个字面值或变量名才对。这种机制主要是为了让代码写起来更方便、读起来更清晰。

​ 第二个问题是,如果用了 *args 之后,又要给函数添加新的位置参数,那么原有的调用操作需要全都更新。例如给参数列表开头添加新的位置参数 sequence,那么没有根此更新的那些调用代码就会出错。

python 复制代码
def log(sequence, message, *values):
    if not values:
        print(f'{sequence} - {message}')
    else:
        values_str = ','.join(str(x) for x in values)
        print(f'{sequence} - {message}: {values_str}')


log(1, 'Favorites', 7, 33)      # New with *args OK
log(1, 'Hi there')              # New message only OK
log('Favorite numbers', 7, 33)  # Old usage breaks

>>>
1 - Favorites: 7,33
1 - Hi there
Favorite numbers - 7: 33

​ 问题在于:第三次调用 log 函数的那个地方并没有根据新的参数列表传入 sequence 参数,所以 'Favorite numbers' 就成了 sequence 参数,7 就成了 message 参数。这样的 bug 很难排查,因为程序不会抛出异常,只会采用错我的数据继续运行下去。为了彻底避免这种漏洞,在这种 *args 函数添加参数时,应该使用只能通过关键字来指定的参数(keyword-only-argument,参见 第25条)。要是想做得更稳妥一些,可以考虑添加类型注释(参见 第90条)。

相关推荐
喵了几个咪5 分钟前
GoWind CMS 拆解:在 Go 里给 TipTap 搭一套 Yjs 协同后端
开发语言·后端·golang
程序员清风23 分钟前
数据处理脚本工程化:日志、配置、命令行与测试
服务器·开发语言·数据库
在世修行2 小时前
智学文枢:页面切换与跨页数据预填
python·桌面应用·signal·1024程序员节·pyside·跨页跳转·状态预填
2601_962885722 小时前
如何用 Python 把 K 线重采样成任意周期?
开发语言·python
打工仔折腾 AI2 小时前
在 Ubuntu ARM64 部署 Q1 V3.0 MuJoCo 模型:加载、单步推理与执行器响应解析
linux·运维·人工智能·python·深度学习·ubuntu·性能优化
Wang's Blog3 小时前
Java 项目实战: 交友项目 - 圈子功能实现:总结
java·开发语言·交友
信誓旦旦的程序猿3 小时前
【Python量化实战宝典 #21】北向资金跟随手册:聪明钱择时信号(Python 可跑)
python·1024程序员节·股票数据api·股票数据api接口·股票api数据接口·股票量化数据api·股票量化数据接口
严谨的麻辣烫3 小时前
静态 ISP 代理请求超时怎么排查?Python 与 curl 实战
python·http·requests·1024程序员节·网络排障
下一站春风里3 小时前
位运算完整学习笔记:按位与(&)+ 异或(^)
开发语言·c++·算法
天外飞雨道沧桑4 小时前
我在浏览器里练了一个 792 参数的大模型,它学会了补全 JavaScript
开发语言·javascript·ai·ecmascript·ai编程