传递任意数量的实参

有时你不会提前知道一个函数需要接受多少参数。 幸运的是,Python 允许函数从调用语句中收集任意数量的参数。

例如,考虑一个制作披萨的函数。 它需要接受一定数量的配料,但你无法提前知道一个人想要多少配料。 以下示例中的函数有一个参数 *toppings,但此参数收集的参数与调用行提供的参数一样多:

def make_pizza(*toppings):
    """Print the list of toppings that have been requested."""
    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):
    """Summarize the pizza we are about to make."""
    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

无论函数接收多少参数,此语法都有效。

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

如果你想让一个函数接受几种不同类型的参数,接受任意数量参数的参数必须放在函数定义的最后。 Python 首先匹配位置参数和关键字参数,然后收集最终参数中的所有剩余参数。

例如,如果函数需要接受比萨饼的 size ,则该参数必须位于参数 *toppings 之前:

def make_pizza(size, *toppings):
    """Summarize the pizza we are about to make."""
    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 。 函数调用首先包含一个大小参数,然后根据需要添加尽可能多的配料。

现在每个披萨都有一个 size 和一些配料(top),每条信息都打印在适当的位置,先显示大小,再显示配料:

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,它收集像这样的任意位置参数。

使用任意数量的命名实参

有时你会想要接受任意数量的参数,但你不会提前知道什么样的信息将传递给函数。 在这种情况下,您可以编写接受调用语句提供的尽可能多的键值对的函数。 一个例子涉及建立用户档案:你知道你会得到关于用户的信息,但你不确定你会收到什么样的信息。 以下示例中的函数 build_profile() 始终接受名字和姓氏,但它也接受任意数量的命名参数:

def build_profile(first, last, **user_info):
    """Build a dictionary containing everything we know about a user."""
    user_info['first_name'] = first (1)
    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' 传递给它。 我们将返回的 profile 文件分配给 user_profile 并打印 user_profile:

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

返回的字典包含用户的名字和姓氏,在本例中还包含位置和研究领域。 无论在函数调用中提供了多少个额外的键值对,该函数都将起作用。

在编写自己的函数时,您可以通过多种不同方式混合位置值、关键字值和任意值。 了解所有这些参数类型的存在很有用,因为当您开始阅读其他人的代码时,您会经常看到它们。 正确使用不同类型并知道何时使用每种类型需要练习。 现在,请记住使用最简单的方法来完成工作。 随着您的进步,您将学会每次都使用最有效的方法。

你会经常看到用于收集非特定命名参数的参数名称 **kwargs

自己试试
8-12.Sandwiches

编写一个函数,接受一个人想要的三明治清单。 该函数应该有一个参数,该参数收集与函数调用提供的一样多的项目,并且它应该打印正在订购的三明治的摘要。 调用该函数三次,每次使用不同数量的参数。

8-13.User Profile

从第 148 页的 user_profile.py 副本开始。通过调用 build_profile(),使用您的名字和姓氏以及其他三个描述您的键值对来构建您自己的配置文件。

8-14.Cars

编写一个函数,将有关汽车的信息存储在字典中。 该功能应始终接收制造商和型号名称。 然后它应该接受任意数量的关键字参数。 使用所需信息和其他两个名称-值对(例如颜色或可选功能)调用该函数。 您的函数应该适用于这样的调用:

car = make_car('subaru', 'outback', color='blue', tow_package=True)

打印返回的字典以确保所有信息都正确存储。