python中函数参数传递的几种方法

Python 中函数参数传递的几种方法

Python 中函数参数的传递,本质上是把对象绑定到函数的形参名上,也可以理解为通过“赋值”来传递。这个规则回答了参数传递的总体原则,但还需要进一步说明:函数参数如何定义,以及函数调用时参数如何解析。

函数参数的定义常见有四种形式:

  1. F(arg1, arg2, ...)
  2. F(arg2=<value>, arg3=<value>, ...)
  3. F(*args)
  4. F(**kwargs)

1. 普通位置参数

第一种方式是最传统的方式。函数可以定义若干个参数,形式参数放在函数名后的小括号中,各个参数之间用逗号隔开。

用这种方式定义的函数,在调用时必须提供相同数量的实际参数,不能多也不能少,并且顺序必须对应。也就是说,形参和实参之间是一一对应的关系:形参1 = 实参1形参2 = 实参2,依此类推。

例如:

def add_on(x, y):
    return x + y

这个函数可以这样调用:

add_on(1, 2)

这表示形参 x 取值为 1,形参 y 取值为 2。而下面两种调用方式都是错误的:

add_on(1, 2, 3)  # 参数太多
add_on(1)        # 参数太少

2. 默认参数与关键字参数

第二种方式是在定义函数时给形参指定默认值。调用函数时,如果没有给对应的形参传递实参,那么这个形参就使用默认值。

例如:

def add_on(x=3, y=5):
    return x + y

调用:

add_on(6, 5)

表示 x 取值为 6y 取值为 5

调用:

add_on(7)

表示 x 取值为 7y 使用默认值 5

这时会出现一个问题:如果想让 x 使用默认值,只给 y 赋值怎么办?这就要用到 Python 函数调用中的关键字参数:

add_on(y=6)

这表示 x 使用默认值 3y 取值为 6

关键字参数可以直接指定形参名,因此不必遵守参数定义时的先后顺序。例如:

add_on(y=4, x=6)

这种通过形参名进行定点赋值的方式,对于只使用普通位置参数定义的函数同样适用。

需要注意的是,默认参数有一个常见陷阱:不要把可变对象作为默认值,例如列表、字典等。因为默认值只会在函数定义时创建一次,后续调用会复用同一个对象。

3. 可变位置参数 *args

前两种方式定义的形参个数都是固定的。例如定义函数时如果定义了 5 个形参,那么调用时最多也只能传递 5 个实参。

但实际编程中,并不总能提前确定一个函数会接收多少个参数。第三种方式就是用来应对这种情况的。它使用一个星号加形参名表示,例如 *args。这个函数可以接收任意数量的位置参数,可以是 0 个,也可以是 N 个。传入的这些参数会在函数内部组成一个元组。

例如:

def add_on(*args):
    total = 0
    for value in args:
        total += value
    return total

下面这些调用方式都是可以的:

add_on()
add_on(2)
add_on(3, 4, 5, 6)

在函数内部,args 分别对应:

()
(2,)
(3, 4, 5, 6)

4. 可变关键字参数 **kwargs

与第三种方式类似,形参名前面加两个星号,表示接收任意数量的关键字参数。这些参数会在函数内部组成一个字典。

调用这种函数时,必须采用 key=value 的形式传参。例如:

def add_on(**kwargs):
    total = 0
    if len(kwargs) == 0:
        return 0
    for value in kwargs.values():
        total += value
    return total

调用方式可以是:

add_on()
add_on(x=4, y=5, k=6)

其中 add_on(x=4, y=5, k=6) 在函数内部得到的 kwargs 是:

{'x': 4, 'y': 5, 'k': 6}

如果使用 Python 2,字典遍历也可以写成 kwargs.itervalues();在 Python 3 中应使用 kwargs.values()

组合使用参数

上面四种参数定义方式可以组合在一起,形成更复杂也更灵活的函数定义形式。定义函数时通常要遵循下面的顺序:

  1. 普通位置参数,例如 arg
  2. 默认参数,例如 arg=<value>
  3. 可变位置参数,例如 *args
  4. 可变关键字参数,例如 **kwargs

也就是说:

def func(arg, default_arg=value, *args, **kwargs):
    pass

在函数调用过程中,形参赋值的大致过程是:

  1. 先按顺序把普通位置实参赋给对应的形参。
  2. 再把 arg=<value> 这种形式的关键字实参赋给对应形参。
  3. 多出来的位置实参组成一个元组,赋给带一个星号的形参。
  4. 多出来的关键字实参组成一个字典,赋给带两个星号的形参。

听起来复杂,实际很直观。看下面这个例子:

def test(x, y=5, *a, **b):
    print(x, y, a, b)

不同调用方式的结果如下:

test(1)                  # 1 5 () {}
test(1, 2)               # 1 2 () {}
test(1, 2, 3)            # 1 2 (3,) {}
test(1, 2, 3, 4)         # 1 2 (3, 4) {}

test(x=1)                # 1 5 () {}
test(x=1, y=1)           # 1 1 () {}
test(x=1, y=1, a=1)      # 1 1 () {'a': 1}
test(x=1, y=1, a=1, b=1) # 1 1 () {'a': 1, 'b': 1}

test(1, y=1)             # 1 1 () {}
test(1, 2, y=1)          # TypeError: test() got multiple values for argument 'y'
test(1, 2, 3, 4, a=1)   # 1 2 (3, 4) {'a': 1}

其中 test(1, 2, y=1) 会出错,因为位置参数 2 已经赋给了 y,后面又通过关键字参数 y=1 再次给 y 赋值,因此 Python 会提示 y 获得了多个值。

总结一下:普通位置参数适合固定参数数量的函数;默认参数适合给常用值设置缺省行为;args 适合接收不定数量的位置参数;kwargs 适合接收不定数量的关键字参数。实际编写函数时,应尽量让参数设计清晰,只有在确实需要灵活扩展时再使用 args**kwargs

Leave a Reply