Python函数实参与形参详解
函数基础概念回顾
在深入探讨 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
在这个函数中,a
和 b
是位置形参。当调用 add_numbers
函数时,传入的实参将按照位置顺序依次赋给 a
和 b
。比如:
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}")
在这个函数中,age
和 city
是命名关键字形参。调用这个函数时必须使用关键字参数的形式传入 age
和 city
的值:
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 就是位置实参,它们按照顺序分别传递给了 a
和 b
形参。
关键字实参
关键字实参是通过形参的名称来传递值的实参。这种方式允许我们不按照形参的定义顺序传递实参。例如,对于 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=32
和 city="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
这里 x
和 y
是在函数外部定义的变量,作为实参传递给 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
的值并没有因为函数调用而改变,因为 a
是 num
值的副本,在函数内部对 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_list
和 lst
指向同一个列表对象,所以在函数内部对 lst
的修改会影响到函数外部的 my_list
。
实参的内存管理
实参在函数调用之前已经在内存中存在。当函数调用结束后,实参本身的内存管理遵循 Python 的垃圾回收机制。如果实参是函数内部创建的临时对象(如通过解包生成的临时可迭代对象),那么在函数调用结束后,这些临时对象可能会被垃圾回收。例如:
def add_numbers(*args):
total = sum(args)
return total
result = add_numbers(*[1, 2, 3])
这里 *[1, 2, 3]
生成的临时列表在函数调用结束后,如果没有其他引用指向它,可能会被垃圾回收。
总结形参与实参的要点
- 形参定义:包括位置形参、默认形参、命名关键字形参、可变长度形参(
*args
和**kwargs
),每种形参有其特定的用途和规则。 - 实参传递:有位置实参、关键字实参、混合实参以及解包实参等方式,传递时要遵循相应的匹配规则。
- 作用域:形参作用域在函数内部,实参作用域取决于其定义位置,两者在函数执行过程中相互配合,但作用域范围不同。
- 内存管理:不可变类型形参是实参值的副本,可变类型形参是实参的引用,实参本身的内存管理遵循 Python 垃圾回收机制。
通过深入理解 Python 函数的形参与实参,开发者能够编写出更加灵活、健壮且易于维护的代码。无论是小型脚本还是大型项目,对形参与实参的熟练运用都是关键的编程技能之一。在实际编程中,根据具体的需求选择合适的形参和实参传递方式,能够提高代码的可读性和效率,避免许多潜在的错误。