6. 函数进阶
6.1 函数参数

引子

在第四章中,我们已经初步学习了函数的概念和用法,知道了如何在 Python 中定义与使用函数;在第五章中,我们又不断地调用各种函数来处理不同的数据类型,大家应该已经对函数有了一定的理解。但是,由于函数这个概念在 Python 中非常重要,因此,我们有必要多花点时间来深入学习一下函数的各个方面。

基本概念

让我们首先来复习一下函数的基本定义。

在 Python 中,函数是一段可重用的代码块,用于执行特定的任务。我们可以使用 def 关键字来定义函数,例如:

def greet(name):
    print("Hello, " + name)
 
greet("Tom")  # 输出:Hello, Tom
greet("Jane")  # 输出:Hello, Jane

其中,函数的定义包括函数名参数列表函数体

  • 函数名是函数的唯一标识,由开发人员来定义。
  • 参数列表是函数所需的输入,可以是一个或多个参数。
  • 函数体是函数所要执行的代码,可以包括多行语句。

我们还可以使用 return 关键字来向调用函数的代码返回值:

def add(x, y):
    return x + y
 
result = add(3, 4)
print(result)  # 输出:7

在复习了函数的基本概念之后,我们将在本节详细讲解函数中参数的用法。

基本用法

首先,在 Python 中,函数的参数是用来接受输入的值的。函数可以接受一个或多个参数,并且参数可以是任意类型的值。

当我们在使用 def 关键字来定义函数时,可以同时指定参数列表:

def greet(name, greeting):
    print(greeting + ", " + name)
 
greet("Tom", "Hello")  # 输出:Hello, Tom
greet("Jane", "Hi")  # 输出:Hi, Jane

上面的代码定义了一个名为 greet 的函数,接受两个参数:namegreeting。在函数调用时,使用者需要提供与参数列表中所定义的参数数量相同的值。

从不同的角度看,我们可以将 Python 中的函数参数类型分为以下几种:

  • 位置参数
  • 关键字参数
  • 默认参数
  • 可变参数
  • 限定关键字参数
  • 限定位置参数
  • 参数组合

下面,就让我们来一一讲解。

位置参数

在 Python 中,位置参数(Positional Parameter)是指函数所接受的固定数量的参数,按照它们在参数列表中的顺序来解析。

一般情况下,我们在使用 def 关键字来定义函数时,给出的就是位置参数列表,如同前面的例子所演示的:

def greet(name, greeting):
    print(greeting + ", " + name)
 
greet("Tom", "Hello")  # 输出:Hello, Tom
greet("Jane", "Hi")  # 输出:Hi, Jane

上面的代码定义了一个名为 greet 的函数,接受的两个参数就是位置参数:namegreeting

在函数调用时,使用者需要提供与参数列表中所定义的参数数量相同的值,并按照它们在参数列表中的顺序来解析。由于使用者需要严格按照参数列表定义的顺序提供对应的参数,所以,我们就把这种参数类型就叫做位置参数。

位置参数是 Python 中最常用的参数类型,因为它能简单而有效地传递输入。

关键字参数

在使用位置参数时,我们只需要按照位置顺序提供参数即可;但如果一个函数的参数较多,使用者就可能很难记住所有的参数顺序,而在使用的时候出现错误。例如,在上面的例子中,使用者可能会忘记到底是需要把“Tom”放在“Hello”的前面,还是把“Hello”放在“Tom”的前面。

为了避免这种情况,Python 引入了关键字参数类型。

在 Python 中,关键字参数(Keyword Parameter)是指函数调用时,参数名和参数值都是显式指定的参数。

我们可以通过在调用函数时指定参数名和参数值的方式,来使用关键字参数:

def greet(name, greeting):
    print(greeting + ", " + name)
 
greet(name="Tom", greeting="Hello")  # 输出:Hello, Tom
greet(greeting="Hi", name="Jane")  # 输出:Hi, Jane

此例中的函数与前面示例中的函数是一样的,但是我们在调用它时,显式地指定了参数名和参数值。这样,我们的调用函数看起来就非常直接和明了,不会出现忘记参数顺序的问题。

另外,关键字参数还有一个好处,就是使用者可以忽略参数列表的实际顺序。在函数调用时,使用者可以以任意顺序提供参数名和参数值。在上例中,greet(greeting="Hi", name="Jane")greet(name="Jane", greeting="Hi") 是等价的。

总之,相比于位置参数,关键字参数可以给予调用者更好的可读性和更好的调用体验。

默认参数

在上面的示例中,我们在调用函数 greet() 的时候,每次都需要提供 greeting 参数,用来显示欢迎语。假设我们的欢迎语始终都是“Hello”,那么每次调用都需要提供“Hello”就会显得很繁琐。在这种情况下,我们就需要使用默认参数了。

在 Python 中,默认参数(Default Parameter)是指在定义函数时为参数指定默认值的参数。

我们可以使用 def 关键字来定义函数,并指定默认参数值:

def greet(name, greeting="Hello"):
    print(greeting + ", " + name)
 
greet("Tom")  # 输出:Hello, Tom
greet("Tom", "Hi")  # 输出:Hi, Tom

在上面的代码中,我们为函数 greet() 的参数 greeting 指定了一个默认值:"Hello"。这样,如果使用者不提供 greeting 参数,函数就使用默认的参数值 “Hello” 来代替;而如果使用者提供了参数值,那么函数就会使用提供的参数值。

在实际开发中,默认参数非常有用,因此它能简化函数的定义,并且在调用函数时给予使用者更好的体验。

可变参数

在前面的示例中,我们在定义函数时指定的参数都是固定的。例如,greet() 函数有且仅有两个参数,我们可以按照下面的方式调用 greet("Tom", "Hello"),但不能使用这样的方式 greet("Tom", "Jerry", "Hello"),虽然,我们希望同时欢迎多个人员。

为了解决这样的问题,Python 提供了可变长度参数。

在 Python 中,可变参数(Variable-length Parameter)是指函数能接受任意数量的参数的参数。

Python 提供了两种特殊的运算符来定义可变参数: *** 。在实际开发中,我们通常使用 *args**kwargs 来定义。这不是 Python 的命名规则,只是约定俗成的使用惯例。

  • *args:允许函数接受可变数量的位置参数。传递给 *args 的参数会被收集到一个元组中。

    def my_function(*args):
        # 使用传递给 *args 的参数执行函数的代码
     
    my_function(1, 2, 3, 4)  # args = (1, 2, 3, 4)
  • **kwargs:允许函数接受可变数量的关键字参数。传递给 **kwargs 的参数会被收集到一个字典中。

    def my_function(**kwargs):
        # 使用传递给 **kwargs 的参数执行函数的代码
     
    my_function(a=1, b=2, c=3)  # kwargs = {'a': 1, 'b': 2, 'c': 3}

在 Python 中使用可变参数,能够为我们提供以下的好处:

  • 允许函数接受任意数量的位置参数
    如前所述,在 Python 中使用可变参数的常见用例之一是当我们想要定义一个可以接受任意数量位置参数的函数的时候。例如,假如我们需要一个函数能够允许多个文件名作为输入,我们就可以使用 *args 语法来定义此类函数:

    def print_files(*file_names):
        for file_name in file_names:
            with open(file_name) as f:
                print(f.read())
     
    print_files("file1.txt", "file2.txt", "file3.txt")

    在此示例中,print_files() 函数使用 *file_names 语法接受可变数量的位置参数。函数然后逐个打开每个文件并打印其内容。通过使用可变参数,我们可以轻松地将任何数量的文件名传递给函数,而不必手动逐个指定它们。

  • 允许函数接受任意数量的关键字参数
    在 Python 中使用可变参数的另一个用例是当我们需要一个函数能够接受任意数量的关键字参数的时候。例如,我们可能有一个函数需要将可变数量的配置选项作为输入,这时我们就可以使用 **kwargs 语法来定义此类函数:

    def configure(**options):
        for key, value in options.items():
            print(f"Setting {key} to {value}")
     
    configure(database="mydb", username="myuser", password="mypassword")

    在此例中,configure() 函数使用 **options 语法接受可变数量的关键字参数。函数然后打印传入的每个键值对。通过使用可变参数,我们可以轻松地将任意数量的配置选项传递给函数,而不必为每个选项定义单独的参数。

  • 允许函数同时组合位置和关键字参数
    如果我们需要一个函数可以同时可变数量的接受位置和可变数量的关键字参数,我们就可以同时使用 *args 语法和 **kwargs 语法。

    def process_inputs(*args, **kwargs):
        # 处理任何位置参数
        for arg in args:
            # 对 arg 进行某些处理
            pass
     
        # 处理任何关键字参数
        for key, value in kwargs.items():
            # 对 key 和 value 进行某些处理
            pass
     
    process_inputs(1, 2, 3, database="mydb", username="myuser", password="mypassword")

    在此例中,process_inputs() 函数使用 *args 语法接受任意数量的位置参数和 **kwargs 语法接受任意数量的关键字参数。函数然后逐个处理每个参数,首先处理任何位置参数,然后处理任何关键字参数。通过这样的位置和关键字参数的组合,我们可以创建一个非常灵活的函数,可以接受各种类型的输入。

总之,在 Python 中使用可变参数可以让我们能够编写更灵活、能够接受更广泛类型输入的函数。通过有效地使用 *args**kwargs,我们可以创建非常强大、而又易于使用的函数。

限定关键字参数

我们在前面学习关键字参数时,可以看到,其本质上是位置参数的变体;也就是说,我们可以不适用关键字参数来传递参数值,而只是简单地使用位置参数。但是,如果我们需要指定函数调用者必须使用关键字参数来传递参数值时,该怎么办呢?此时,就需要限定关键字参数出场了。

在 Python 中,限定关键字参数(keyword-only parameter)是指在函数定义时,参数名和参数值必须使用关键字参数形式指定的参数。很多时候,限定关键字参数也被称为“命名关键字参数”。

我们可以在函数定义时使用 * 来声明限定关键字参数列表:

def greet(*, name, greeting):
    print(greeting + ", " + name)
 
greet(name="Tom", greeting="Hello")  # 输出:Hello, Tom
greet(greeting="Hi", name="Jane")  # 输出:Hi, Jane

上面的代码定义了一个名为 greet() 的函数,接受两个命名关键字参数:namegreeting

由于在这两个参数前面使用了 * 来声明命名关键字参数,因此,namegreeting 皆为命名关键字。也就是说,在函数调用时,调用者必须使用关键字参数的形式来传递参数值。

不过,命名关键字参数是 Python 中一种很少使用的参数类型,它主要用于强制使用关键字参数的形式调用函数,以提高可读性和调用体验。

限定位置参数

在 Python 3.8 之前,函数定义中的所有参数都被视为“positional-or-keyword”,也就是“既可以是-位置-也可以是-关键字”参数;这就意味着它们可以作为位置参数传递或作为关键字参数传递。不过,Python 3.8 引入了一个新的概念,允许开发人员使用 / 分隔符来定义限定位置参数。

在 Python 中,限定位置参数(Positional-only Parameter)是只能作为位置参数传递、而不能作为关键字参数传递的参数。如果需要在函数中定义限定位置参数参数,我们只需在参数列表中将参数名放在 / 分隔符之前即可。

以下是一个示例:

def my_func(pos1, pos2, /, pos3):
    print(f"pos_only1: {pos1}")
    print(f"pos_only2: {pos2}")
    print(f"pos3: {pos3}")

在这个示例中,/ 分隔符出现在前两个参数 pos1pos2 之后,表明这两个参数必须作为位置参数、按顺序传递;而第三个参数 pos3 出现在 / 分隔符之后,表示它既可以作为位置参数传递,也可以作为关键字参数传递。

以下是一个调用该函数的示例:

my_func(1, 2, pos3=3)

输出的内容如下:

pos_only1: 1
pos_only2: 2
pos3: 3

在此例中,我们将值 12 作为位置参数传递,而将 3 作为关键字参数传递。由于 pos1pos2 是限定位置参数,因此我们不能将它们作为关键字参数传递。

参数组合

在 Python 中,我们可以使用上述的多种参数类型来定义函数。

例如,我们可以定义一个函数,同时使用位置参数、默认参数和关键字参数:

def greet(greeting, name="world", punctuation="!"):
    print(greeting + ", " + name + punctuation)
 
greet("Hello")  # 输出:Hello, world!
greet("Hi", "Tom")  # 输出:Hi, Tom!
greet("Welcome", punctuation=".")  # 输出:Welcome, world.

上面的代码定义了一个名为 greet() 的函数,接受一个位置参数和两个可选参数。在函数调用时,调用者可以根据需要提供参数值,或者使用默认参数值。

再看一个例子,我们可以定义一个函数,同时使用位置参数、默认参数、可变参数、关键字参数、限定关键字参数和限定位置参数:

def example_function(pos1, pos2, /, kwarg=None, *, kwonlyarg=False, **kwargs):
    print(f"pos1: {pos1}")
    print(f"pos2: {pos2}")
    print(f"kwarg: {kwarg}")
    print(f"kwonlyarg: {kwonlyarg}")
    print(f"kwargs: {kwargs}")

在这个函数中,我们有:

  • 两个位置参数( pos1pos2 )在 / 分隔符之前定义。它们必须按顺序在调用函数时提供。
  • 一个关键字参数( kwarg ),默认值为 None。此参数可以在调用函数时省略,如果未提供,则默认为 None
  • 一个限定关键字参数( kwonlyarg ),默认值为 False。此参数也可以在调用函数时省略,如果未提供,则默认为 False
  • 一个可变长度关键字参数( **kwargs )在 kwonlyarg 参数之后定义。该参数可以接受任意数量的额外关键字参数,并将它们收集到一个字典中。

以下是一个调用此函数的示例,其中使用了不同类型的参数:

example_function(1, 2, "hello", kwonlyarg=True, x=6, y=7, z=8)
# 或者
example_function(1, 2, kwarg="hello", kwonlyarg=True, x=6, y=7, z=8)

输出的内容如下所示:

pos1: 1
pos2: 2
kwarg: hello
kwonlyarg: True
kwargs: {'x': 6, 'y': 7, 'z': 8}

在此例中,我们按照正确的顺序提供了必需的限定位置参数 pos1pos2;我们还为关键字参数 ( kwarg )和限定关键字参数 ( kwonlyarg)传递了参数值;我们包括了一些额外的关键字参数( x=6y=7z=8 ),这些参数都被收集到了 kwargs 字典中。

总结

综上所述,在 Python 中,函数是通过参数接受输入的值的,函数可以接受一个或多个参数,并且参数可以是任意类型的值。

在实际使用中,我们可以将 Python 中的函数参数类型分为以下几种:

  • 位置参数
  • 关键字参数
  • 默认参数
  • 可变参数
  • 限定关键字参数
  • 限定位置参数
  • 参数组合

通过熟练地使用不同类型的参数,我们可以编写出非常灵活的函数,从而让我们的代码更易于重用和维护。