5.9 KiB
doc_type, full_text
| doc_type | full_text |
|---|---|
| short | sources/01_Variable_arguments.md |
01_Variable_arguments 总结
本文讲解 Python 函数中的可变参数机制,包括位置可变参数 *args、关键字可变参数 **kwargs,以及如何用 * 和 ** 将元组、字典展开为函数调用参数。这些技巧常用于编写更灵活的函数接口、对象构造、包装器和参数透传。相关主题可进一步整理为 Python函数参数、可变参数、参数解包、函数包装器。
核心内容
位置可变参数:*args
函数定义中使用 *args 可以接收任意数量的额外位置参数:
def f(x, *args):
...
调用:
f(1, 2, 3, 4, 5)
结果是:
# x -> 1
# args -> (2, 3, 4, 5)
也就是说,除普通参数 x 之外的额外位置参数会被收集到一个元组中。
关键字可变参数:**kwargs
函数定义中使用 **kwargs 可以接收任意数量的额外关键字参数:
def f(x, y, **kwargs):
...
调用:
f(2, 3, flag=True, mode='fast', header='debug')
结果是:
# x -> 2
# y -> 3
# kwargs -> {'flag': True, 'mode': 'fast', 'header': 'debug'}
额外的关键字参数会被收集到一个字典中。
同时使用 *args 和 **kwargs
可以同时接收任意数量的位置参数和关键字参数:
def f(*args, **kwargs):
...
调用:
f(2, 3, flag=True, mode='fast', header='debug')
函数内部得到:
# args -> (2, 3)
# kwargs -> {'flag': True, 'mode': 'fast', 'header': 'debug'}
这种形式可以接收几乎任意组合的函数参数,常见于:
- 编写包装函数或装饰器
- 将参数原样传递给另一个函数
- 为函数接口预留扩展能力
这与 函数包装器 和 参数透传 密切相关。
参数解包:传入元组和字典
使用 * 展开元组
如果已有一个元组,可以在调用函数时用 * 将其展开为位置参数:
numbers = (2, 3, 4)
f(1, *numbers) # 等价于 f(1, 2, 3, 4)
这在从文件、数据库或其他数据源读入结构化记录后非常有用。
使用 ** 展开字典
如果已有一个字典,可以用 ** 将其展开为关键字参数:
options = {
'color': 'red',
'delimiter': ',',
'width': 400
}
f(data, **options)
# 等价于 f(data, color='red', delimiter=',', width=400)
这种技巧适合将配置项、解析选项或对象字段直接传给函数。
练习要点
练习 7.1:简单的可变参数函数
定义一个计算平均值的函数:
def avg(x, *more):
return float(x + sum(more)) / (1 + len(more))
示例:
avg(10, 11) # 10.5
avg(3, 4, 5) # 4.0
avg(1, 2, 3, 4, 5, 6) # 3.5
这里 x 保证至少有一个参数,*more 收集剩余参数。这种写法适合需要“至少一个值,但可接收更多值”的函数。
练习 7.2:用元组和字典创建对象
假设有一条股票数据:
data = ('GOOG', 100, 490.1)
如果直接调用:
s = Stock(data)
会失败,因为 Stock.__init__() 期望多个独立参数,而不是一个元组。正确做法是:
s = Stock(*data)
如果数据是字典:
data = {'name': 'GOOG', 'shares': 100, 'price': 490.1}
s = Stock(**data)
这要求字典键名与构造函数参数名一致。
练习 7.3:简化实例列表创建
原始代码中从字典列表构建 Stock 对象:
portfolio = [Stock(d['name'], d['shares'], d['price']) for d in portdicts]
可以改写为:
portfolio = [Stock(**d) for d in portdicts]
这样更简洁,也更直接表达“字典字段映射到构造函数参数”的意图。
练习 7.4:参数透传
read_portfolio() 可以通过 **opts 接收额外选项,并传递给 fileparse.parse_csv():
def read_portfolio(filename, **opts):
with open(filename) as lines:
portdicts = fileparse.parse_csv(
lines,
select=['name', 'shares', 'price'],
types=[str, int, float],
**opts
)
portfolio = [Stock(**d) for d in portdicts]
return Portfolio(portfolio)
这样调用者可以控制底层解析函数的行为,例如:
port = report.read_portfolio('Data/missing.csv')
默认显示错误;也可以通过额外参数关闭错误输出:
port = report.read_portfolio('Data/missing.csv', silence_errors=True)
这体现了 **kwargs 在接口扩展和参数透传中的作用。
关键概念
*args:收集额外位置参数,结果是元组。**kwargs:收集额外关键字参数,结果是字典。*tuple:调用函数时将元组展开为位置参数。**dict:调用函数时将字典展开为关键字参数。- 参数透传:外层函数接收可选参数,并传递给内层函数。
- 对象构造简化:当字典键与构造函数参数名一致时,可用
ClassName(**dict)创建对象。
实践意义
本文的主要价值在于展示 Python 函数调用模型的灵活性。掌握 *args、**kwargs 和参数解包后,可以:
- 编写参数数量不固定的函数。
- 简化从结构化数据创建对象的代码。
- 让高层函数暴露底层函数的可选行为。
- 编写更通用的包装器和适配函数。
- 降低重复的字段访问代码,提高可维护性。
这些技巧是理解 Python 函数接口设计、库封装和数据驱动对象构造的重要基础。