MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Python函数实参与形参详解

2023-01-236.0k 阅读

函数基础概念回顾

在深入探讨 Python 函数的实参与形参之前,我们先来回顾一下函数的基本概念。函数是组织好的、可重复使用的、用于执行特定任务的代码块。通过将代码封装成函数,可以提高代码的可读性、可维护性以及可重用性。在 Python 中,定义一个函数使用 def 关键字,如下所示:

def greet():
    print("Hello, World!")

这个简单的 greet 函数不接受任何参数,每次调用它时都会打印出 “Hello, World!”。

形参(形式参数)

形参的定义

形参(formal parameter)是在函数定义时列出的参数。它们就像是函数内部的变量占位符,在函数调用时会被实际的值所替代。例如,我们定义一个接受名字作为参数的 greet_name 函数:

def greet_name(name):
    print(f"Hello, {name}!")

这里的 name 就是形参。它告诉函数在调用时需要传入一个值,这个值将被用于个性化问候语。

位置形参

位置形参是最常见的形参类型。它们按照定义的顺序依次接收实参的值。例如,我们定义一个计算两个数之和的函数 add_numbers

def add_numbers(a, b):
    return a + b

在这个函数中,ab 是位置形参。当调用 add_numbers 函数时,传入的实参将按照位置顺序依次赋给 ab。比如:

result = add_numbers(3, 5)
print(result)  # 输出 8

这里,3 被赋给 a,5 被赋给 b,然后函数返回它们的和。

默认形参

默认形参是在定义函数时为形参指定了默认值的参数。如果在函数调用时没有为这些形参提供实参,那么它们将使用默认值。例如,我们定义一个设置背景颜色的函数 set_background_color

def set_background_color(color="white"):
    print(f"Setting background color to {color}")

在这个函数中,color 是一个默认形参,它的默认值是 “white”。我们可以这样调用这个函数:

set_background_color()  # 输出 Setting background color to white
set_background_color("blue")  # 输出 Setting background color to blue

当不传入参数调用 set_background_color 时,它会使用默认的 “white” 作为颜色;当传入 “blue” 时,它会使用传入的值。

命名关键字形参

命名关键字形参是 Python 中一种特殊的形参类型。它们要求在函数调用时必须使用关键字参数的形式传入值。在函数定义中,命名关键字形参位于 * 之后(如果有 *args,则位于 *args 之后),或者单独存在(如果没有 *args)。例如:

def print_info(name, *, age, city):
    print(f"Name: {name}, Age: {age}, City: {city}")

在这个函数中,agecity 是命名关键字形参。调用这个函数时必须使用关键字参数的形式传入 agecity 的值:

print_info("Alice", age=30, city="New York")

如果不使用关键字参数的形式,例如 print_info("Alice", 30, "New York"),将会引发 TypeError

可变长度形参

*args(可变位置参数)

*args 用于收集所有位置实参,并将它们作为一个元组传递给函数。在函数定义中,*args 通常用于不确定函数会接收多少个位置参数的情况。例如,我们定义一个计算多个数平均值的函数 calculate_average

def calculate_average(*args):
    if not args:
        return 0
    total = sum(args)
    return total / len(args)

我们可以这样调用这个函数:

average1 = calculate_average(1, 2, 3)
average2 = calculate_average(10, 20, 30, 40)
print(average1)  # 输出 2.0
print(average2)  # 输出 25.0

在调用 calculate_average 时,传入的所有位置参数都被收集到 args 元组中,然后函数可以对这些参数进行处理。

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

**kwargs 用于收集所有关键字实参,并将它们作为一个字典传递给函数。在函数定义中,**kwargs 通常用于不确定函数会接收多少个关键字参数的情况。例如,我们定义一个打印用户信息的函数 print_user_info

def print_user_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

我们可以这样调用这个函数:

print_user_info(name="Bob", age=25, country="USA")

在调用 print_user_info 时,传入的所有关键字参数都被收集到 kwargs 字典中,然后函数可以遍历这个字典并打印出键值对。

实参(实际参数)

实参的传递

实参(actual parameter)是在函数调用时传递给函数的值。它们会按照函数定义中形参的顺序和类型,将值传递给相应的形参。例如,对于前面定义的 greet_name 函数:

def greet_name(name):
    print(f"Hello, {name}!")

greet_name("Charlie")

这里的 “Charlie” 就是实参,它被传递给了 greet_name 函数的 name 形参。

位置实参

位置实参是按照函数定义中形参的顺序依次传递给函数的实参。这是最常见的实参传递方式。例如,对于 add_numbers 函数:

def add_numbers(a, b):
    return a + b

result = add_numbers(5, 3)
print(result)  # 输出 8

这里的 5 和 3 就是位置实参,它们按照顺序分别传递给了 ab 形参。

关键字实参

关键字实参是通过形参的名称来传递值的实参。这种方式允许我们不按照形参的定义顺序传递实参。例如,对于 print_info 函数:

def print_info(name, age, city):
    print(f"Name: {name}, Age: {age}, City: {city}")

print_info(city="Los Angeles", age=28, name="David")

这里使用关键字实参的方式,明确指定了每个实参对应的形参,所以顺序可以与形参定义顺序不同。

混合使用位置实参和关键字实参

在函数调用时,可以混合使用位置实参和关键字实参。但是有一个规则:位置实参必须在关键字实参之前。例如,对于 print_info 函数:

print_info("Ella", age=32, city="Chicago")

这里 “Ella” 是位置实参,age=32city="Chicago" 是关键字实参,这种调用方式是合法的。

解包实参

使用 * 解包可迭代对象作为位置实参

在函数调用时,可以使用 * 对可迭代对象(如列表、元组)进行解包,将其元素作为位置实参传递给函数。例如,对于 add_numbers 函数:

numbers = [4, 6]
result = add_numbers(*numbers)
print(result)  # 输出 10

这里使用 *numbers 对列表 numbers 进行解包,将 4 和 6 作为位置实参传递给 add_numbers 函数。

使用 ** 解包字典作为关键字实参

在函数调用时,可以使用 ** 对字典进行解包,将其键值对作为关键字实参传递给函数。例如,对于 print_user_info 函数:

user_info = {
    "name": "Frank",
    "age": 22,
    "country": "Canada"
}
print_user_info(**user_info)

这里使用 **user_info 对字典 user_info 进行解包,将字典中的键值对作为关键字实参传递给 print_user_info 函数。

形参与实参的匹配规则

位置匹配规则

对于位置形参和位置实参,它们按照顺序进行匹配。实参的数量必须与位置形参的数量完全一致(除非有形参有默认值或者使用了可变长度形参)。例如,对于 add_numbers 函数,如果调用时传入的实参数量不正确:

def add_numbers(a, b):
    return a + b

# 以下调用会引发 TypeError
add_numbers(3)  
add_numbers(3, 5, 7) 

关键字匹配规则

对于关键字实参,它们通过形参的名称进行匹配。关键字实参的名称必须与函数定义中的形参名称完全一致。例如,对于 print_info 函数:

def print_info(name, age, city):
    print(f"Name: {name}, Age: {age}, City: {city}")

# 以下调用会引发 TypeError
print_info(n="Grace", a=27, c="Boston") 

混合匹配规则

当混合使用位置实参和关键字实参时,位置实参先按照顺序匹配位置形参,然后关键字实参按照名称匹配剩余的形参。例如,对于 print_info 函数:

print_info("Hank", city="Dallas", age=24)

这里 “Hank” 作为位置实参匹配 name 形参,city="Dallas"age=24 作为关键字实参匹配对应的形参。

可变长度形参匹配规则

对于 *args,它会收集所有剩余的位置实参。对于 **kwargs,它会收集所有剩余的关键字实参。例如,对于下面的函数:

def collect_args(*args, **kwargs):
    print("Positional arguments:", args)
    print("Keyword arguments:", kwargs)

collect_args(1, 2, 3, name="Ivy", age=21)

这里 1、2、3 被收集到 args 元组中,name="Ivy"age=21 被收集到 kwargs 字典中。

形参与实参的作用域

形参的作用域

形参的作用域仅限于函数内部。在函数外部,无法直接访问形参。例如:

def multiply_numbers(a, b):
    result = a * b
    return result

# 以下代码会引发 NameError
print(a)  

这里在函数外部尝试访问形参 a 会引发 NameError,因为 a 的作用域只在 multiply_numbers 函数内部。

实参的作用域

实参本身的作用域取决于它们在函数调用之前的定义位置。如果实参是在函数外部定义的变量,那么它们的作用域按照正常的 Python 作用域规则确定。例如:

x = 5
y = 3

def divide_numbers():
    result = x / y
    return result

print(divide_numbers())  # 输出 1.6666666666666667

这里 xy 是在函数外部定义的变量,作为实参传递给 divide_numbers 函数(虽然这里没有显式地将它们作为实参传递,但它们在函数内部被使用)。它们的作用域在整个模块中,所以函数可以访问它们。

形参与实参的内存管理

形参的内存分配

当函数被调用时,形参在函数的栈帧中被分配内存空间。对于不可变类型(如整数、字符串)的形参,它们的值是实参值的副本。例如:

def modify_number(a):
    a = a + 1
    return a

num = 10
new_num = modify_number(num)
print(num)  # 输出 10
print(new_num)  # 输出 11

这里 num 的值并没有因为函数调用而改变,因为 anum 值的副本,在函数内部对 a 的修改不会影响到函数外部的 num

对于可变类型(如列表、字典)的形参,它们是实参的引用。例如:

def modify_list(lst):
    lst.append(4)
    return lst

my_list = [1, 2, 3]
new_list = modify_list(my_list)
print(my_list)  # 输出 [1, 2, 3, 4]
print(new_list)  # 输出 [1, 2, 3, 4]

这里 my_listlst 指向同一个列表对象,所以在函数内部对 lst 的修改会影响到函数外部的 my_list

实参的内存管理

实参在函数调用之前已经在内存中存在。当函数调用结束后,实参本身的内存管理遵循 Python 的垃圾回收机制。如果实参是函数内部创建的临时对象(如通过解包生成的临时可迭代对象),那么在函数调用结束后,这些临时对象可能会被垃圾回收。例如:

def add_numbers(*args):
    total = sum(args)
    return total

result = add_numbers(*[1, 2, 3])

这里 *[1, 2, 3] 生成的临时列表在函数调用结束后,如果没有其他引用指向它,可能会被垃圾回收。

总结形参与实参的要点

  1. 形参定义:包括位置形参、默认形参、命名关键字形参、可变长度形参(*args**kwargs),每种形参有其特定的用途和规则。
  2. 实参传递:有位置实参、关键字实参、混合实参以及解包实参等方式,传递时要遵循相应的匹配规则。
  3. 作用域:形参作用域在函数内部,实参作用域取决于其定义位置,两者在函数执行过程中相互配合,但作用域范围不同。
  4. 内存管理:不可变类型形参是实参值的副本,可变类型形参是实参的引用,实参本身的内存管理遵循 Python 垃圾回收机制。

通过深入理解 Python 函数的形参与实参,开发者能够编写出更加灵活、健壮且易于维护的代码。无论是小型脚本还是大型项目,对形参与实参的熟练运用都是关键的编程技能之一。在实际编程中,根据具体的需求选择合适的形参和实参传递方式,能够提高代码的可读性和效率,避免许多潜在的错误。