在python中,可以定义一个采用任意数量的位置参数的函数,如下所示:
def f(*args):
print(args)
f(1, 2, 3) # (1, 2, 3)
当称为f(a, b, c)
时,所有位置参数都组合成一个 tuple 。
python 2和3文档中对此行为进行了描述,但是我还没有找到PEP。
PEP 3132,在“接受”条件下引入了扩展的可迭代拆包(first, *middle, last = seqence
)状态
被讨论。如果我写一个包装器,我可能还想进一步处理这样的参数:将加星标的目标设为元组而不是列表。这将与函数的* args一致,但会使结果的进一步处理变得更加困难。
def force_type(position, type):
def wrapper(f):
def new(*args, **kwargs):
args = list(args) # Why?
args[position] = type(args[position])
return f(*args, **kwargs)
return new
return wrapper
@force_type(1, int)
def func(a, b, c):
assert isinstance(b, int)
args
是tuple
的事实使进一步的处理变得更加困难。只是在引入包装的早期阶段不使用包装纸吗?如果是这样,为什么在python3中没有其他兼容性破坏性更改(PEP3132倾向于简化处理而不是一致性(这似乎至少类似于兼容性破坏性更改中的兼容性),所以这种变化)。
为什么功能*args
(仍然)还是tuple
,即使list
允许更轻松地进行进一步处理?
答案 0 :(得分:1)
为什么不呢?关于元组的事情是,创建后不能更改它。这样可以提高执行脚本的速度,并且您实际上不需要功能参数列表,因为您实际上不需要修改函数的给定参数。 您是否需要为参数添加或删除方法?大多数情况下不会。您是否希望程序运行速度更快。那是肯定的。这就是大多数人更喜欢拥有东西的方式。 * args之所以返回元组,是因为如果您确实需要一个列表,则可以用一行代码对其进行转换!
args = list(args)
因此,通常: 它可以加快程序执行速度。您不必更改参数。更改其类型并不难。
答案 1 :(得分:1)
我不知道这是否是其背后的想法,但是这种易于处理的方式(即使用list
数据实例化一个tuple
也不是那么难)会导致混乱的行为
def fce1(*args):
fce2(args)
# some more code using args
def fce2(args):
args.insert(0, 'other_val')
fce1(1, 2, 3)
编写fce1
代码的人如果没有意识到后来要处理的args
不是该函数所调用的内容,可能会感到惊讶。
我还假定不可变类型在内部更易于处理,并且开销较小。
答案 2 :(得分:1)
我的最佳猜测是,如果* args生成一个list(可变),则在多种情况下都可能导致非常令人惊讶的结果。 @Ondrej K.举了一个很好的例子。打个比方,当将列表作为默认参数时,每个函数调用都可能具有不同的默认参数。这是默认参数仅被评估一次的结果,这种情况并不是最直观的。甚至官方的python文档也针对这种情况提供了一种特定的解决方法。
执行功能定义时,从左到右评估默认参数值。这意味着在定义函数时,表达式将被计算一次,并且每次调用均使用相同的“预计算”值。这对于理解默认参数何时是可变对象(例如列表或字典)尤其重要:如果函数修改了该对象(例如,通过将项目附加到列表),则默认值实际上已被修改。这通常不是预期的。解决此问题的一种方法是使用None作为默认值,并在函数主体中明确测试它,例如:
def whats_on_the_telly(penguin=None):
if penguin is None:
penguin = []
penguin.append("property of the zoo")
return penguin
总而言之,我认为* args是元组,因为将其作为列表会引起与可变类型相关的所有问题(例如速度较慢),而更大的问题是大多数人不希望函数参数发生变化。 尽管我确实同意此实现与PEP-3132非常不一致,并且会引起大多数学习者的困惑。我刚接触Python,花了一段时间才明白* args是元组而不是列表的原因,以便与PEP-3132's接受保持一致。