传递实参

因为函数定义可以有多个参数,所以函数调用可能需要多个参数。 您可以通过多种方式将参数传递给您的函数。 您可以使用位置参数,它需要与参数的写入顺序相同; 命名参数,其中每个参数由一个变量名和一个值组成; 以及值的列表和字典。 让我们依次看看这些。

位置实参

调用函数时,Python 必须将函数调用中的每个参数与函数定义中的参数相匹配。 最简单的方法是根据提供的参数的顺序。 以这种方式匹配的值称为位置参数。

要了解其工作原理,请考虑一个显示宠物信息的函数。 该函数告诉我们每只宠物是什么动物以及宠物的名字,如下所示:

def describe_pet(animal_type, pet_name): (1)
    """Display information about a pet."""
    print(f"\nI have a {animal_type}.")
    print(f"My {animal_type}'s name is {pet_name.title()}.")

describe_pet('hamster', 'harry') (2)
1 定义表明这个函数需要一种动物和动物的名字。
2 当我们调用 describe_pet() 时,我们需要按顺序提供动物类型和名称。 例如,在函数调用中,参数’hamster’被分配给参数animal_type,参数’harry’被分配给参数pet_name。 在函数体中,这两个参数用于显示被描述宠物的信息。

输出描述了一只名为 Harry 的仓鼠(hamster):

I have a hamster.
My hamster's name is Harry.

多次函数调用

您可以根据需要多次调用一个函数。 描述第二只不同的宠物只需要再调用一次 describe_pet():

def describe_pet(animal_type, pet_name):
    """Display information about a pet."""
    print(f"\nI have a {animal_type}.")
    print(f"My {animal_type}'s name is {pet_name.title()}.")

describe_pet('hamster', 'harry')
describe_pet('dog', 'willie')

在这第二个函数调用中,我们向 describe_pet() 传递参数“dog”和“willie”。 与我们使用的前一组参数一样,Python 将“dog”与参数 animal_type 匹配,将“willie”与参数 pet_name 匹配。 和以前一样,这个函数完成了它的工作,但这次它打印了一只名叫 Willie 的狗的值。 现在我们有一只名叫哈利的仓鼠和一只名叫威利的狗:

I have a hamster.
My hamster's name is Harry.

I have a dog.
My dog's name is Willie.

多次调用一个函数是一种非常有效的工作方式。 描述宠物的代码在函数中只写一次。 然后,任何时候你想描述一只新宠物,你都可以用新宠物的信息调用这个函数。 即使描述宠物的代码扩展到 10 行,您仍然可以通过再次调用该函数仅用一行来描述新宠物。

位置参数中的顺序很重要

如果在使用位置参数时混淆函数调用中参数的顺序,您可能会得到意想不到的结果:

def describe_pet(animal_type, pet_name):
    """Display information about a pet."""
    print(f"\nI have a {animal_type}.")
    print(f"My {animal_type}'s name is {pet_name.title()}.")

describe_pet('harry', 'hamster')

在此函数调用中,我们首先列出名称,其次列出动物的类型。 因为这次首先列出参数“harry”,所以该值被分配给参数 animal_type。 同样,'hamster' 被分配给 pet_name。 现在我们有一个名为“Hamster”的“harry”:

I have a harry.
My harry's name is Hamster.

如果您得到这样有趣的结果,请检查以确保函数调用中参数的顺序与函数定义中参数的顺序相匹配。

命名实参

命名参数是传递给函数的名称-值对。 您直接将参数中的名称和值相关联,因此当您将参数传递给函数时,不会产生混淆(您最终不会得到一个名为 Hamster 的哈利)。 关键字参数使您不必担心在函数调用中正确排序参数,并且它们阐明了每个值在函数调用中的作用。

让我们使用关键字参数重写 pets.py 来调用 describe_pet():

def describe_pet(animal_type, pet_name):
    """Display information about a pet."""
    print(f"\nI have a {animal_type}.")
    print(f"My {animal_type}'s name is {pet_name.title()}.")

describe_pet(animal_type='hamster', pet_name='harry')

函数 describe_pet() 没有改变。 但是当我们调用该函数时,我们明确告诉 Python 每个参数应该与哪个参数匹配。 当 Python 读取函数调用时,它知道将参数“hamster”分配给参数 animal_type,将参数“harry”分配给 pet_name。 输出正确地显示我们有一只名为 Harry 的仓鼠。

关键字参数的顺序无关紧要,因为 Python 知道每个值应该放在哪里。 以下两个函数调用是等价的:

describe_pet(animal_type='hamster', pet_name='harry')
describe_pet(pet_name='harry', animal_type='hamster')

使用命名参数时,请务必使用函数定义中参数的确切名称。

默认值

编写函数时,可以为每个参数定义一个默认值。 如果在函数调用中提供了参数的参数,Python 将使用参数值。 如果不是,它使用参数的默认值。 因此,当您为参数定义默认值时,您可以排除通常在函数调用中编写的相应参数。 使用默认值可以简化您的函数调用并阐明您的函数通常使用的方式。

例如,如果您注意到大多数对 describe_pet() 的调用都用于描述狗,则可以将 animal_type 的默认值设置为 'dog'。 现在,任何为狗调用 describe_pet() 的人都可以省略该信息:

def describe_pet(pet_name, animal_type='dog'):
    """Display information about a pet."""
    print(f"\nI have a {animal_type}.")
    print(f"My {animal_type}'s name is {pet_name.title()}.")

describe_pet(pet_name='willie')

我们更改了 describe_pet() 的定义以包含 animal_type 的默认值“dog”。 现在,当函数在没有指定 animal_type 的情况下被调用时,Python 知道使用值 'dog' 作为这个参数:

I have a dog.
My dog's name is Willie.

请注意,必须更改函数定义中参数的顺序。 因为默认值不需要指定动物类型作为参数,所以函数调用中唯一剩下的参数就是宠物的名字。 Python 仍然将其解释为位置参数,因此如果仅使用宠物的名字调用该函数,该参数将与函数定义中列出的第一个参数匹配。 这就是第一个参数需要是 pet_name 的原因。

现在使用这个函数最简单的方法是在函数调用中只提供一只狗的名字:

describe_pet('willie')

此函数调用将具有与前一个示例相同的输出。 提供的唯一参数是“willie”,因此它与定义中的第一个参数 pet_name 匹配。 因为没有为 animal_type 提供参数,Python 使用默认值 'dog'。

要描述除狗以外的动物,您可以使用如下函数调用:

describe_pet(pet_name='harry', animal_type='hamster')

因为提供了 animal_type 的显式参数,Python 将忽略参数的默认值。

当您使用默认值时,任何具有默认值的参数都需要列在所有没有默认值的参数之后。 这允许 Python 继续正确解释位置参数。

等效的函数调用

因为位置参数、命名参数和默认值都可以一起使用,所以你通常会有几种等效的方法来调用一个函数。 考虑以下 describe_pet() 定义,其中提供了一个默认值:

def describe_pet(pet_name, animal_type='dog'):

使用此定义,始终需要为 pet_name 提供参数,并且可以使用位置或命名参数格式提供此值。 如果所描述的动物不是狗,则调用中必须包含 animal_type 的参数,并且也可以使用位置或命名参数格式指定此参数。

以下所有调用都适用于此函数:

# A dog named Willie.
describe_pet('willie')
describe_pet(pet_name='willie')

# A hamster named Harry.
describe_pet('harry', 'hamster')
describe_pet(pet_name='harry', animal_type='hamster')
describe_pet(animal_type='hamster', pet_name='harry')

这些函数调用中的每一个都将具有与前面示例相同的输出。

使用哪种调用方式并不重要。 只要你的函数调用产生你想要的输出,就使用你觉得最容易理解的风格。

避免实参错误

当您开始使用函数时,如果遇到参数不匹配的错误,请不要感到惊讶。 当您提供的参数少于或多于函数完成其工作所需的参数时,就会出现不匹配的参数。 例如,如果我们尝试不带参数调用 describe_pet() 会发生以下情况:

def describe_pet(animal_type, pet_name):
    """Display information about a pet."""
    print(f"\nI have a {animal_type}.")
    print(f"My {animal_type}'s name is {pet_name.title()}.")

describe_pet()

Python 识别出函数调用中缺少一些信息,回溯告诉我们:

Traceback (most recent call last):
  File "pets.py", line 6, in <module> (1)
    describe_pet() (2)
    ^^^^^^^^^^^^^^
TypeError: describe_pet() missing 2 required positional arguments: (3)
  'animal_type' and 'pet_name'
1 回溯首先告诉我们问题的位置,让我们回头看看我们的函数调用出了什么问题。
2 接下来,有问题的函数调用被写出来给我们看。
3 最后,回溯告诉我们调用缺少两个参数,并报告缺少参数的名称。 如果这个函数在一个单独的文件中,我们可能可以正确地重写调用,而不必打开该文件并阅读函数代码。

Python 很有帮助,因为它为我们读取函数的代码并告诉我们需要提供的参数的名称。 这是给变量和函数起描述性名称的另一个动机。 如果这样做,Python 的错误消息将对您和可能使用您的代码的任何其他人更有用。

如果你提供了太多的参数,你应该得到一个类似的回溯,它可以帮助你正确地将你的函数调用与函数定义相匹配。

自己试试
8-3.T-Shirt

编写一个名为 make_shirt() 的函数,它接受尺寸和应该印在衬衫上的消息文本。 该函数应打印一句话总结衬衫的尺寸和印在上面的信息。 使用位置参数调用函数一次来制作衬衫。 使用关键字参数第二次调用该函数。

8-4.Large Shirts

修改 make_shirt() 函数,使衬衫默认为大号,并带有一条消息:我喜欢 Python。 制作带有默认信息的大号衬衫和中号衬衫,以及带有不同信息的任意尺码的衬衫。

8-5.Cities

编写一个名为 describe_city() 的函数,它接受城市名称及其国家/地区。 该函数应该打印一个简单的句子,例如 Reykjavik is in Iceland。 为国家/地区的参数指定默认值。 为三个不同的城市调用您的函数,其中至少一个不在默认国家/地区。