Python:函数参数

207 阅读5分钟

1. 参数值是否可修改

先来看两个基本概念:形参实参,在定义函数的语句中,两个括号之间的变量通常成为形参,而在函数调用时提供的值成为实参。在函数调用阶段,通常是仅使用参数的值,而不会去改变它,例如:

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

example_function(3, 5) # 8

那么传入的参数到底能不能改变呢?答案是:部分可改变,部分不可改变,具体得看情况

  1. 对于字符串、数字和元祖类型的参数来讲,函数内的修改不会影响函数外的变量值。
def unchanged_parameter(str_parameter: str,
                        int_parameter: int,
                        tuple_parameter: tuple):
    """字符串、数字和元祖不可改变"""

    str_parameter = "new value"
    int_parameter = 0
    tuple_parameter = (1, 2)


if __name__ == '__main__':
    str_example = "example"
    int_example = 1
    tuple_example = (0, 1)

    unchanged_parameter(str_example, int_example, tuple_example)

    print(str_example) # example
    print(int_example) # 1
    print(tuple_example) # (0, 1)
  1. 对于可变的数据结构(例如:字典、列表)类型的参数来讲,函数内的修改会影响函数外的变量
def variable_parameter(dict_parameter: dict,
                       list_parameter: list):
    """可变数据结构类型(如:字典、列表)的参数可改变"""

    dict_parameter["new"] = "new value"
    list_parameter[0] = 1
    
if __name__ == '__main__':
    ### 可变示例
    dict_example = {
        "old": "old value"
    }
    list_example = [0, 1, 2, 4]
    variable_parameter(dict_example, list_example)

    print(dict_example)
    print(list_example)

对于不可变参数这种情况,如果的确需要函数在执行后修改对应参数的值,则需要将修改的值返回并重新赋值给这个变量,例如:

def change_int_variable(a):
    return a + 2

a = 2
a = change_int_variable(2)
print(a) # 4

对于可变参数这种情况,在函数执行过程中会修改参数的值,但是函数执行过程后依旧想保持参数原来的值,则需要在传参之前添加一个复制的步骤得到原来数据的副本,并将副本作为参数传递给函数。以列表为例:

list_example = [0, 1, 2, 4]
list_example_copied = list_example[:]

def change_list_value(list_parameter):
    list_parameter[0] = 1
    
change_list_value(list_example_copied)
print(list_example_copied) # [1, 1, 2, 4]
print(list_example) # [0, 1, 2, 4]

2. 位置参数和关键字参数

上面的看到的示例,都是位置参数,参数的位置比它们的名字更重要。可以这么理解位置参数:实参和形参的位置要一一对应,如果函数调用时,实参的调用位置发生变化,对函数的执行效果有巨大的影响。

def example_function(a, b):
    return a + ", " + b

a = "Hello"
b = "world"
print(example_function(a, b)) # Hello, world
print(example_function(b, a)) # world, Hello

可以看到,对于上面的函数example_function,在调用时,如果参数ab实参位置发生了变化,最终的执行结果也发生了变化。对于函数参数不多的时候,我们可以记住这些参数的具体的位置,但是一旦函数参数过多,就会带来很差的体验,降低开发效率。这种时候,在调用函数时,可以提供参数的名称,例如:

print(example_function(a=a, b=b)) # Hello, world
print(example_function(b=b, a=a)) # Hello, world

可以看到,改变参数的位置也不会影响函数的执行和输出,但需要注意实参和形参一定要对应。我们称这种参数的使用方式为关键字参数

3. 参数默认值

带有默认值的参数也称为缺省参数,在函数声明时,为形参提供一个默认值;在函数调用时,对应的实参可传也可不传,传递参数时,会覆盖该参数的默认值,例如:

def example_function(a, b=1):
    return a ** b

a = 2
b = 3
print(example_function(a)) # 2
print(example_function(a, b)) # 8

为参数提供默认值时,有一点需要注意:带有默认值的参数,一定要放在参数列表的末尾,无论是函数定义时还是调用函数时,否则会提示错误。

4. *args 和 **kwargs

上面的几个例子,无论是位置参数还是关键字参数,函数的参数个数都是固定的。但在实际过程中,经常会出现函数需要接收任意数量的参数的需求,这种情况下*args**kwargs就派上了用场。

  1. *args全称为Non-keyword Variable Arguments,无关键字参数,当函数中以列表或者元祖的方式传参时,使用*args。当*args和位置参数一起使用时,优先给位置参数分配参数,再将多余的参数以元祖的形式分配给*args*args不能和关键字参数一起使用。

示例:

def example_args_function(a, b, *args):
    print(a)
    print(b)
    print(args)
    
example_args_function(1, 2, 3, "a", [3, 4], (5, 6))
# 1
# 2
# (3, 'a', [3, 4], (5, 6)) 
  1. **kwargs全称为keyword Variable Arguments,有关键字参数,当以字典的方式传参时,使用**kwargs**kwargs可以与位置参数、关键词参数一起使用。

示例:

def example_kwargs_function(a, b, **kwargs):
    print(a)
    print(b)
    print(kwargs)
    
example_kwargs_function(1, 2, c=3, d=4)
# 1
# 2
# {'c': 3, 'd': 4}
example_kwargs_function(1, b=2, c=3, d=4)
# 1
# 2
# {'c': 3, 'd': 4}
  1. 位置参数、带默认值的位置参数、*args**kwargs一起声明式,它们的前后顺序为:位置参数、带默认值的位置参数、带默认值的位置参数、*args**kwargs。但是,带默认值参数、*args**kwargs一起使用时,通常情况下都会覆盖这个默认值,所以,一般在和*args**kwargs一起使用时,会忽略带默认值的位置参数
def example_with_mixed_arguments(a, b=1, *args, **kwargs):
    print(a)
    print(b)
    print(args)
    print(kwargs)
    
example_with_mixed_arguments(1)
# 1
# 1
# 
# {}

example_with_mixed_arguments(1, 2)
# 1
# 2
# 
# {}

example_with_mixed_arguments(1, 2, 3, 4)
# 1
# 2
# (3, 4)
# {}

example_with_mixed_arguments(1, 2, 3, 4, c=5, d=6)
# 1
# 2
# (3, 4)
# {'c'=5, 'd'=6}

example_with_mixed_arguments(1, 2, 3, 4, b=7, c=5, d=6)
# TypeError: example_with_mixed_arguments() got multiple values for argument 'b'

example_with_mixed_arguments(1, 2, 3, 4, c=5, d=6)
# 如果想让b保留默认值,把2、3、4赋值给args,这是不可实现的。

5. *args和**kwagrs的逆操作

上一小节讲述了*args**kwargs的“正向”操作:函数在定义时,使用***表示任意数量参数的收集,*用元祖的方式收集不匹配的位置参数,**用字典的形式收集关键字参数。所谓逆操作或者反操作,是指在函数调用时实参中使用***,这时是用来解包参数的,*将元祖或者列表解包为位置参数,相对应的,**将字典解包为关键字参数。例如:

def example_reverse_function(a, b):
    print(a ** b)

a = 2
b = 3
example_reverse_function(*(a, b))
# 8

c = {
    'a': a,
    'b': b
}
example_reverse_function(**c)
# 8