8.5 传递任意数量的实参

有时候,预先不知道函数需要接受多少个实参,好在Python允许函数从调用语句中收集任意数量的实参。

例如,来看一个制作比萨的函数,它需要接受很多配料,但无法预先确定顾客要多少种配料。下面的函数只有一个形参*toppings ,但不管调用语句提供了多少实参,这个形参会将它们统统收入囊中:

pizza.py

def make_pizza(*toppings):
"""打印顾客点的所有配料。"""
print(toppings)

make_pizza('pepperoni')
make_pizza('mushrooms', 'green peppers', 'extra cheese')


形参名*toppings 中的星号让Python创建一个名为toppings 的空元组,并将收到的所有值都封装到这个元组中。函数体内的函数调用print() 通过生成输出,证明Python能够处理使用一个值来调用函数的情形,也能处理使用三个值来调用函数的情形。它以类似的方式处理不同的调用。注意,Python将实参封装到一个元组中,即便函数只收到一个值:

('pepperoni',)
('mushrooms', 'green peppers', 'extra cheese')


现在,可以将函数调用print() 替换为一个循环,遍历配料列表并对顾客点的比萨进行描述:

def make_pizza(*toppings):
"""概述要制作的比萨。"""
print("\nMaking a pizza with the following toppings:")
for topping in toppings:
print(f"- {topping}")

make_pizza('pepperoni')
make_pizza('mushrooms', 'green peppers', 'extra cheese')


不管收到一个值还是三个值,这个函数都能妥善处理:

Making a pizza with the following toppings:
- pepperoni

Making a pizza with the following toppings:
- mushrooms
- green peppers
- extra cheese


不管函数收到的实参是多少个,这种语法都管用。

8.5.1 结合使用位置实参和任意数量实参

如果要让函数接受不同类型的实参,必须在函数定义中将接纳任意数量实参的形参放在最后。Python先匹配位置实参和关键字实参,再将余下的实参都收集到最后一个形参中。

例如,如果前面的函数还需要一个表示比萨尺寸的形参,必须将其放在形参*toppings 的前面:

def make_pizza(size, *toppings):
"""概述要制作的比萨。"""
print(f"\nMaking a {size}-inch pizza with the following toppings:")
for topping in toppings:
print(f"- {topping}")

make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')


基于上述函数定义,Python将收到的第一个值赋给形参size ,并将其他所有值都存储在元组toppings 中。在函数调用中,首先指定表示比萨尺寸的实参,再根据需要指定任意数量的配料。

现在,每个比萨都有了尺寸和一系列配料,而且这些信息按正确的顺序打印出来了——首先是尺寸,然后是配料:

Making a 16-inch pizza with the following toppings:
- pepperoni

Making a 12-inch pizza with the following toppings:
- mushrooms
- green peppers
- extra cheese


注意  你经常会看到通用形参名*args ,它也收集任意数量的位置实参。

8.5.2 使用任意数量的关键字实参

有时候,需要接受任意数量的实参,但预先不知道传递给函数的会是什么样的信息。在这种情况下,可将函数编写成能够接受任意数量的键值对——调用语句提供了多少就接受多少。一个这样的示例是创建用户简介:你知道将收到有关用户的信息,但不确定会是什么样的信息。在下面的示例中,函数build_profile() 接受名和姓,还接受任意数量的关键字实参:

user_profile.py

def build_profile(first, last, **user_info):
"""创建一个字典,其中包含我们知道的有关用户的一切。"""
❶ user_info['first_name'] = first
user_info['last_name'] = last
return user_info

user_profile = build_profile('albert', 'einstein',
location='princeton',
field='physics')
print(user_profile)


函数build_profile() 的定义要求提供名和姓,同时允许根据需要提供任意数量的名称值对。形参**user_info 中的两个星号让Python创建一个名为user_info 的空字典,并将收到的所有名称值对都放到这个字典中。在这个函数中,可以像访问其他字典那样访问user_info 中的名称值对。

build_profile() 的函数体内,将名和姓加入了字典user_info 中(见❶),因为总是会从用户那里收到这两项信息,而这两项信息没有放到这个字典中。接下来,将字典user_info 返回到函数调用行。

我们调用build_profile() ,向它传递名('albert' )、姓('einstein' )和两个键值对(location='princeton'field='physics' ),并将返回的user_info 赋给变量user_profile ,再打印该变量:

{'location': 'princeton', 'field': 'physics',
'first_name': 'albert', 'last_name': 'einstein'}


在这里,返回的字典包含用户的名和姓,还有求学的地方和所学专业。调用这个函数时,不管额外提供多少个键值对,它都能正确地处理。

编写函数时,能以各种方式混合使用位置实参、关键字实参和任意数量的实参。知道这些实参类型大有裨益,因为阅读别人编写的代码时经常会见到它们。要正确地使用这些类型的实参并知道其使用时机,需要经过一定的练习。就目前而言,牢记使用最简单的方法来完成任务就好了。继续往下阅读,你就会知道在各种情况下哪种方法的效率最高。

注意  你经常会看到形参名**kwargs ,它用于收集任意数量的关键字实参。
动手试一试
练习8-12:三明治  编写一个函数,它接受顾客要在三明治中添加的一系列食材。这个函数只有一个形参(它收集函数调用中提供的所有食材),并打印一条消息,对顾客点的三明治进行概述。调用这个函数三次,每次都提供不同数量的实参。
练习8-13:用户简介  复制前面的程序user_profile.py,在其中调用build_profile() 来创建有关你的简介。调用这个函数时,指定你的名和姓,以及三个描述你的键值对。
练习8-14:汽车  编写一个函数,将一辆汽车的信息存储在字典中。这个函数总是接受制造商和型号,还接受任意数量的关键字实参。这样调用该函数:提供必不可少的信息,以及两个名称值对,如颜色和选装配件。这个函数必须能够像下面这样进行调用:
car = make_car('subaru', 'outback', color='blue', tow_package=True)


打印返回的字典,确认正确地处理了所有的信息。