参数传递

函数参数的作用就是传递数据给函数用。在调用函数时,将函数外的数据传递给函数参数,其作用相当于为函数参数赋值。

形式参数和实际参数#

为了便于叙述,通常把函数参数区分为形式参数和实际参数:

  • 形式参数(Parameters):在定义函数时,函数名后面括号中的参数,简称形参
  • 实际参数(Arguments):在调用函数时,函数名后面括号中的参数,简称实参
def mid_point(p1, p2):  # 这里的 p1, p2 为形参
    x = (p1[0] + p2[0]) / 2.0
    y = (p1[1] + p2[1]) / 2.0
    return x, y

a = (0, 0)
b = (14, 16)
x, y = mid_point(a, b)  # 这里的 a, b 为实参
print((x, y))  # (7.0, 8.0)

传递可变对象与不可变对象#

前面已经讲过,Python 中的对象分为不可变的和可变的。与其他语言不同,Python 的变量是没有类型的,而对象有类型。当将一个变量赋值给另外一个变量时,所传递的总是引用而非值。如下所示:

a = 33
b = a  # 传递引用,a 和 b 将指向相同的内存空间
print(id(a))  # 4307682544
print(id(b))  # 4307682544

函数调用就是将实参赋值给形参,也即将实参的引用传递给形参。当实参属于列表、字典、集合这些可变类型时,如果在函数中原位修改参数的值,将导致函数外的值也发生变化。但在函数内部为参数重新赋值并不会影响调用者。见下页示例。

def change(x, y):
    x = 1  # 使函数体内的局部变量 x 指向新的内存空间,对函数外的 a 并无影响
    y[0] = 33  # 原位修改列表 y,将影响函数外的 b
    y = [4, 5, 6]  # 使函数体内的局部变量 y 指向新的内存空间,对函数外的 b 并无影响
    y[0] = 44  # 此时对 y 的修改已经和函数外的 b 无关了
    print(y)  # [44, 5, 6]

a = 6
b = [1, 2, 3]
change(a, b)
print(a)  # 6
print(b)  # [33, 2, 3]

尽管有时在函数中修改可变参数是有用的,但也常常会造成不可预料的后果,因此应尽量避免在函数中修改可变参数。有时,为了杜绝这个问题,可以向函数中传入不可变参数,如将列表转换为元组传递给函数。

参数匹配模式#

Python 定义了多种实参和形参的匹配模式,使我们能更加灵活地定义或调用函数。见下页表。

我们在前面已经接触过很多这类函数,他们的签名如下:

print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)
sorted(iterable, /, *, key=None, reverse=False)

这里 print() 函数参数 object 前面的 * 表示可变长参数,可以为该参数提供以逗号分割的多个值构成的元组,余下的参数均是默认值参数,在函数调用时可以省略,省略时参数取等号后面的默认值。

sorted() 函数中的 / 表示其前方的 iterable 参数必须以常规的位置参数的形式传入,而不能以 name=value 形式的关键字参数传入;其中的 * 表示其后方若传入参数,则必须为关键字参数。

对可迭代对象操作常用的内置函数
语法 位置 解释
func(value) 调用者 常规参数,通过位置匹配
func(name=value) 调用者 关键字参数:通过名称匹配
func(*iterable) 调用者 iterable 中的所有对象作为独立的基于位置的参数传入
func(**dict) 调用者 dict 中的所有键/值对作为独立的关键字参数传入
def func(name) 函数 常规参数,通过位置或名称匹配
def func(name=value) 函数 默认值参数,如果没有在调用时传入的话
def func(*name) 函数 将剩下的基于位置的参数匹配并收集到一个元组中
def func(**name) 函数 将剩下的基于关键字参数匹配并收集到一个字典中
def func(*other, name) 函数 在调用中必须通过关键字传入 name 参数,仅限 3.x
def func(*, name=value) 函数 在调用中必须通过关键字传入 name 参数,仅限 3.x
def func(name, /) 函数 在调用中必须为 name 传入常规的位置参数,仅限 3.x

位置参数也称必备参数必需参数,是必须按照正确的顺序传到函数中,即调用时的数量和位置必须和定义时是一样的。否则将可能抛出异常,或产生与预期不符的结果。如下所示:

def print_score(name, course, score):
    print(f'{name} 同学的 {course} 考了 {score:.1f} 分。')

print_score('小明', '英语', 83.5)  # 小明 同学的 英语 考了 83.5 分。
print_score('英语', '小明', 83.5)  # 英语 同学的 小明 考了 83.5 分。
print_score('小明', 83.5, '英语')  # ValueError: Unknown format code 'f' for object of type 'str'

位置参数是最常用的方法,匹配顺序为从左到右。

关键字参数是指使用形式参数的名字来确定输入的参数值。即在调用函数时使用 name=value 的形式,来指定函数中哪个参数接受某个值。

print_score('小明', score=83.5, course='英语')
print_score(score=83.5, course='英语', name='小明')
print_score(course='英语', name='小明', score=83.5)

# 都将打印:
# 小明 同学的 英语 考了 83.5 分。

上例中第一行混合使用了位置参数和关键字参数,这时位置参数首先按照从左到右的顺序匹配开头的参数。

函数定义中未使用 /* 时,在调用时参数可以按位置或关键字传递给函数。在 Python 3.x 中还可以使用仅限位置参数和仅限关键字参数。

仅限位置参数使用 / 标明其前方的形参必须使用位置参数,不能使用关键字参数的形式。/ 用于在逻辑上分割仅限位置形参与其它形参。

仅限关键字参数使用 * 标明其后方的形参必须是关键字参数,不能使用位置参数的形式。* 用于在逻辑上分割其他形参和仅限关键字形参。

示例:

def f(a, b, /, c, d, *, e, f):
    print(a, b, c, d, e, f)

这里函数 f 的形参 ab 为仅限位置参数,cd 可以是位置参数或关键字参数,而 ef 为仅限关键字参数。以下是该函数的调用示例:

f(4, 'hello', 'world', True, e=33, f=44)
f(4, 'hello', c='world', d=True, e=33, f=44)
# f(a=4, 'hello', 'world', True, e=33, f=44)  # SyntaxError: positional argument follows keyword argument
# f(4, 'hello', c='world', True, e=33, f=44)  # SyntaxError: positional argument follows keyword argument
# f(4, 'hello', 'world', True, 33, f=44)  # TypeError: f() takes 4 positional arguments but 5 positional arguments (and 1 keyword-only argument) were given

默认值参数为没有传入值的可选参数指定默认值。即在定义函数时使用 name=value 的形式,来指定参数的默认值。

def mid_point(p1, p2=(0, 0)):
    x = (p1[0] + p2[0]) / 2.0
    y = (p1[1] + p2[1]) / 2.0
    return x, y

a = (14, 16)
x, y = mid_point(a)
print((x, y))  # (7.0, 8.0)
b = (2, 4)
x, y = mid_point(a, b)
print((x, y))  # (8.0, 10.0)

mid_point() 计算两点之间的中点坐标,如果没有传入第二点坐标,则默认其为坐标原点。

可变长参数又称不定长参数,即传入函数中的实际参数可以是任意多个。定义可变长参数函数时,主要有两种形式,一种是 *name,另一种是 **name

*name 接收任意多个实参并将其放到一个元组中:

def total(*numbers):
    result = 0
    for x in numbers:
        result += x
    return result

num = total(1, 2, 3, 4)
print(num)

**name 接收任意多个类似关键字参数一样显式赋值的实参,并将其放到一个字典中:

def print_score(**scores):
    for name, score in scores.items():
        print(f'{name} 的成绩为 {score:.1f}')

print_score(张三=79, 李四=86, 王老五=92.5)
函数基础变量的作用域