Python 函数形参

PythonBeginner
立即练习

介绍

在这个 Lab 中,我们将探索如何在 Python 中定义和使用带参数的函数。当函数能够接受输入时,它们会变得更加强大,这使得它们具有动态性和可重用性。

我们将介绍几种类型的函数参数,包括位置参数(positional parameters)、默认值(default values)、关键字参数(keyword arguments)以及可变长度参数(variable-length arguments)。通过实际示例,你将学会如何创建灵活的函数来处理各种输入。

这是一个实验(Guided Lab),提供逐步指导来帮助你学习和实践。请仔细按照说明完成每个步骤,获得实际操作经验。根据历史数据,这是一个 初级 级别的实验,完成率为 95%。获得了学习者 100% 的好评率。

定义带有位置形参的函数

参数允许我们将数据传递给函数,使其行为能够根据接收到的输入而变化。我们从最常见的类型开始:位置参数(positional parameters)。传递给函数的实参(arguments)是根据它们的顺序分配给这些形参(parameters)的。

首先,在 WebIDE 左侧的文件浏览器中找到 positional_params.py 文件并打开它。

现在,让我们定义一个名为 hello 的函数,它接受一个参数 name。将以下代码添加到 positional_params.py 文件中:

def hello(name):
    print(f'Hello, {name}!')

hello('John')
hello('Alice')

在这段代码中,name 是一个形参(parameter),它在函数定义中充当一个占位符。当我们调用函数,例如 hello('John') 时,值 'John' 是一个实参(argument),它被赋给 name 形参。

要运行脚本,请通过点击 WebIDE 顶部菜单的 Terminal -> New Terminal 打开一个新的终端。然后,执行以下命令:

python3 ~/project/positional_params.py

你将看到以下输出,表明函数对不同的实参产生了不同的结果:

Hello, John!
Hello, Alice!

如果一个函数是用位置参数定义的,那么在调用它时,你必须为每个参数提供一个对应的实参。忘记提供将会导致错误。

使用默认形参值

为参数提供一个默认值通常很有用。如果在函数调用期间没有为该参数提供实参,则会使用默认值。这使得该参数成为可选的。

让我们看看当我们调用一个函数但没有提供必需的实参时会发生什么。打开 default_params.py 文件,并添加以下代码:

def hello(name):
    print(f'Hello, {name}!')

hello()

保存文件并通过终端运行它:

python3 ~/project/default_params.py

这将产生一个 TypeError,因为函数 hello 期望一个实参但没有接收到任何实参。

Traceback (most recent call last):
  File "/home/labex/project/default_params.py", line 4, in <module>
    hello()
TypeError: hello() missing 1 required positional argument: 'name'

为了修复这个问题,我们可以给 name 形参赋一个默认值。通过将 default_params.py 文件的内容替换为以下代码来修改它:

def hello(name="World"):
    print(f'Hello, {name}!')

hello()
hello("Jobs")

现在,再次运行脚本:

python3 ~/project/default_params.py

输出将是:

Hello, World!
Hello, Jobs!

第一次调用使用了默认值 "World",而第二次调用使用了提供的实参 "Jobs"。

重要规则: 在函数定义中,所有没有默认值的形参必须出现在所有有默认值的形参之前。例如,def func(a, b="default") 是正确的,但 def func(a="default", b) 将导致 SyntaxError

使用关键字传递参数

调用函数时,你可以明确指定你正在提供实参的形参的名称。这些被称为关键字实参(keyword arguments)。它们的主要优点是顺序无关紧要,这可以使你的代码更具可读性。

打开 keyword_args.py 文件,并添加以下代码:

def person(name, age):
    print(f"{name} is {age} years old.")

## 使用关键字实参调用 - 顺序无关紧要
person(name="Zhang San", age=25)
person(age=50, name="Li Si")

保存文件并通过终端运行它:

python3 ~/project/keyword_args.py

你将看到两次调用都能正确工作,无论实参的顺序如何:

Zhang San is 25 years old.
Li Si is 50 years old.

你也可以在一次函数调用中混合使用位置实参和关键字实参。但是,你必须遵守一个规则:所有位置实参必须出现在任何关键字实参之前

现在,用以下内容替换 keyword_args.py 的内容,以查看此规则的实际应用:

def person(name, age):
    print(f"{name} is {age} years old.")

## 这是位置实参和关键字实参的有效混合
person("Wang Wu", age=26)

## 这将导致一个 SyntaxError: positional argument follows keyword argument
## person(age=28, "Zhao Liu")

再次运行脚本。有效的调用将按预期执行。

python3 ~/project/keyword_args.py

预期输出:

Wang Wu is 26 years old.

处理可变数量的参数

Python 函数可以设计为接受可变数量的实参。当你事先不知道将向函数传递多少实参时,这非常有用。

可变位置实参 (*args)

在形参名称前放置一个星号 *,你可以将任意数量的位置实参收集到一个元组(tuple)中。名称 args 是一种约定,但你可以使用任何有效的形参名称。

可变关键字实参 (**kwargs)

在形参名称前放置两个星号 **,你可以将任意数量的关键字实参收集到一个字典(dictionary)中。名称 kwargs 也是一种约定。

让我们结合这些概念。打开 variable_args.py 文件,并添加以下代码:

## 使用 *args 对可变数量的数字求和
def calculate_sum(*numbers):
    print(f"Arguments received as a tuple: {numbers}")
    total = sum(numbers)
    print(f"Sum: {total}\n")

## 使用 **kwargs 捕获额外的个人资料信息
def person_profile(name, age, **other_info):
    print(f"Name: {name}")
    print(f"Age: {age}")
    print(f"Other Info: {other_info}\n")

## 调用函数
calculate_sum(1, 2, 3)
calculate_sum(10, 20, 30, 40, 50)

person_profile('Wang Wu', 26, gender="Male", job="Writer")
person_profile('Zhao Liu', 28, city="Beijing", status="Active")

从终端运行脚本:

python3 ~/project/variable_args.py

输出显示 numbers 是一个元组,而 other_info 是一个字典:

Arguments received as a tuple: (1, 2, 3)
Sum: 6

Arguments received as a tuple: (10, 20, 30, 40, 50)
Sum: 150

Name: Wang Wu
Age: 26
Other Info: {'gender': 'Male', 'job': 'Writer'}

Name: Zhao Liu
Age: 28
Other Info: {'city': 'Beijing', 'status': 'Active'}

探索特殊参数类型

Python 允许你强制规定实参如何传递给函数,使函数的签名更清晰、歧义更少。你可以指定形参为仅位置(positional-only)或仅关键字(keyword-only)。

仅位置形参 (/)

要指定形参只能按位置传递,请在函数定义中将它们放在斜杠(/)之前。

仅关键字形参 (*)

要指定形参只能按关键字传递,请将它们放在星号(*)之后。如果 * 后面没有跟形参名称,则表示所有后续形参都必须是仅关键字的。

让我们看看如何组合这些类型。打开 special_params.py 文件,并添加以下代码。此示例定义了一个包含仅位置形参、标准形参和仅关键字形参的函数。

def school_info(name, /, standard_param, *, city):
    print(f'Positional-Only (name): {name}')
    print(f'Standard (standard_param): {standard_param}')
    print(f'Keyword-Only (city): {city}')
    print('---')

## 在此函数中:
## - `name` 必须按位置传递。
## - `standard_param` 可以按位置或关键字传递。
## - `city` 必须按关键字传递。

## 有效的调用
school_info("Peking University", "PKU", city="Beijing")
school_info("Tsinghua University", standard_param="THU", city="Beijing")

## 无效的调用示例(已注释以防止错误)
## school_info(name="Zhejiang University", "ZJU", city="Hangzhou")  ## TypeError: name is positional-only
## school_info("Fudan University", "FDU", "Shanghai")               ## TypeError: city is keyword-only

运行脚本以查看有效调用的结果:

python3 ~/project/special_params.py

输出将是:

Positional-Only (name): Peking University
Standard (standard_param): PKU
Keyword-Only (city): Beijing
---
Positional-Only (name): Tsinghua University
Standard (standard_param): THU
Keyword-Only (city): Beijing
---

这种语法有助于创建健壮且自文档化的函数,减少调用方式上的歧义。

总结

在此次实验中,你学习了使用 Python 函数形参的基础知识。我们介绍了如何定义和调用带有位置形参的函数,如何通过提供默认值使形参可选,以及如何使用关键字实参以获得更具可读性的代码。你还探索了如何创建灵活的函数,使其能够接受可变数量的位置实参(*args)和关键字实参(**kwargs)。最后,我们研究了特殊语法(/*),用于强制规定实参的传递方式,这提高了函数的清晰度和健壮性。