参数收集是指在软件开发或系统交互过程中,从用户、系统或其他来源获取必要数据的过程。 这些参数是程序运行、功能实现和决策制定的基础输入,直接影响系统的行为和输出结果。
有时候,允许用户提供任意数量的参数很有用。例如, 在本章前面的姓名存储示例中,每次只能存储一个姓名。如果能够像下面这样同时存储多个姓名就好了:
store(data, name1, name2, name3)
为此,应允许用户提供任意数量的姓名。实际上,这实现起来并不难。 请尝试使用下面这样的函数定义:
def print_params(*params):
print(params)
这里好像只指定了一个参数,但它前面有一个星号。这是什么意思呢? 尝试使用一个参数来调用这个函数,看看结果如何。
print_params('Testing')
('Testing',)
注意到打印的是一个元组,因为里面有一个逗号。这么说,前面有星号的参数将被放在元组中?
复数 params
应该提供了线索。
print_params(1, 2, 3)
(1, 2, 3)
参数前面的星号将提供的所有值都放在一个元组中,也就是将这些值收集起来。 这样的行为之前就见过:赋值时带星号的变量收集多余的值。 它收集的是列表而不是元组中多余的值,但除此之外,这两种用法很像。 下面再来编写一个函数:
def print_params_2(title, *params):
print(title)
print(params)
并尝试调用它:
print_params_2('Params:', 1, 2, 3)
Params: (1, 2, 3)
因此星号意味着收集余下的位置参数。如果没有可供收集的参数,params
将是一个空元组。
print_params_2('Nothing:')
Nothing: ()
与赋值时一样,带星号的参数也可放在其他位置(而不是最后),但不同的是, 在这种情况下需要做些额外的工作:使用名称来指定后续参数。
def in_the_middle(x, *y, z):
print(x, y, z)
in_the_middle(1, 2, 3, 4, 5, z=7)
1 (2, 3, 4, 5) 7
in_the_middle(1, 2, 3, 4, 5, 7)
星号不会收集关键字参数。
print_params_2('Hmm...', something=42)
要收集关键字参数,可使用两个星号。
def print_params_3(**params):
print(params)
print_params_3(x=1, y=2, z=3)
{'x': 1, 'y': 2, 'z': 3}
这样得到的是一个字典而不是元组,可结合使用这些技术。
def print_params_4(x, y, z=3, *pospar, **keypar):
print(x, y, z)
print(pospar)
print(keypar)
其效果与预期的相同。
print_params_4(1, 2, 3, 5, 6, 7, foo=1, bar=2)
1 2 3 (5, 6, 7) {'foo': 1, 'bar': 2}
print_params_4(1, 2)
1 2 3 () {}
现在回到最初的问题:如何在姓名存储示例中使用这种技术?解决方案如下:
def lookup(data, label, name):
if data.get(label):
if data.get(label).get(name):
return data.get(label)
else:
return False
else:
return False
def store(data, *full_names):
for full_name in full_names:
names = full_name.split()
if len(names) == 2:
names.insert(1, '')
labels = 'first', 'middle', 'last'
for label, name in zip(labels, names):
people = lookup(data, label, name)
if people:
people.append(full_name)
else:
data[label][name] = [full_name]
这个函数调用起来与只接受一个姓名的前一版一样容易。
d = {}
# init(d)
store(d, 'Han Solo')
但现在也可以这样做:
store(d, 'Luke Skywalker', 'Anakin Skywalker')
lookup(d, 'last', 'Skywalker')
前面介绍了如何将参数收集到元组和字典中,但用同样的两个运算符( *
和 **
)也可执行相反的操作。
与收集参数相反的操作是什么呢?假设有如下函数:
def add(x, y):
return x + y
注意:模块 operator
提供了这个函数的高效版本。
同时假设还有一个元组,其中包含两个要相加的数。
params = (1, 2)
这与前面执行的操作差不多是相反的:不是收集参数,而是分配参数。
这是通过在调用函数时使用运算符 *
实现的。
add(*params)
3
这种做法也可用于参数列表的一部分,条件是这部分位于参数列表末尾。通过使用运算符**
,
可将字典中的值分配给关键字参数。如果像前面那样定义了函数 hello_3
,就可像下面这样做:
params = {'name': 'Sir Robin', 'greeting': 'Well met'}
hello_3(**params)
如果在定义和调用函数时都使用*
或**
,将只传递元组或字典。因此还不如不使用它们,还可省却些麻烦。
def with_stars(**kwds):
print(kwds['name'], 'is', kwds['age'], 'years old')
def without_stars(kwds):
print(kwds['name'], 'is', kwds['age'], 'years old')
args = {'name': 'Mr. Gumby', 'age': 42}
with_stars(**args)
without_stars(args)
Mr. Gumby is 42 years old Mr. Gumby is 42 years old
对于函数 with_stars
,在定义和调用它时都使用了星号,
而对于函数 without_ stars
,在定义和调用它时都没有使用,
但这两种做法的效果相同。因此,
只有在定义函数(允许可变数量的参数)或调用函数时(拆分字典或序列)使用,星号才能发挥作用。
提示:使用这些拆分运算符来传递参数很有用,因为这样无需操心参数个数之类的问题,如下所示:
def foo(x, y, z, m=0, n=0):
print(x, y, z, m, n)
def call_foo(*args, **kwds):
print("Calling foo!")
foo(*args, **kwds)
这在调用超类的构造函数时特别有用。
面对如此之多的参数提供和接受方式,很容易犯晕。下面来看一个综合示例。首先来定义一些函数。
def story(**kwds):
return 'Once upon a time, there was a ' \
'{job} called {name}.'.format_map(kwds)
def power(x, y, *others):
if others:
print('Received redundant parameters:', others)
return pow(x, y)
def interval(start, stop=None, step=1):
'Imitates range() for step > 0'
if stop is None: # 如果没有给参数stop指定值,
start, stop = 0, start # 就调整参数start和stop的值
result = []
i = start # 从start开始往上数
while i < stop: # 数到stop位置
result.append(i) # 将当前数的数附加到result末尾
i += step # 增加到当前数和step(> 0)之和
return result
下面来尝试调用这些函数。
print(story(job='king', name='Gumby'))
Once upon a time, there was a king called Gumby.
print(story(name='Sir Robin', job='brave knight'))
Once upon a time, there was a brave knight called Sir Robin.
params = {'job': 'language', 'name': 'Python'}
print(story(**params))
Once upon a time, there was a language called Python.
del params['job']
print(story(job='stroke of genius', **params))
Once upon a time, there was a stroke of genius called Python.
power(2, 3)
8
power(3, 2)
9
power(y=3, x=2)
8
params = (5,) * 2
power(*params)
3125
power(3, 3, 'Hello, world')
Received redundant parameters: ('Hello, world',)
27
interval(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
interval(1, 5)
[1, 2, 3, 4]
interval(3, 12, 4)
[3, 7, 11]
power(*interval(3, 7))
Received redundant parameters: (5, 6)
81