文章目录

  • 1. 位置参数
  • 2. 可变参数
  • 3. 关键字参数
  • 4. 命名关键字参数
  • 5. 多种参数形式组合
  • 6. 代表任意可能参数的参数列表


注: 本文不会把“默认参数”(实际上应该称之为参数默认值才准确)作为一种独立的参数形式讲解,它实际上只是给参数赋予默认值的一种做法,可应用于位置参数和命名关键字参数等多种场合

1. 位置参数

和所有编程语言类似,Python中的函数参数,最基本的一种就是“位置参数”,也就是按其在函数签名中的生命的顺序来定义和识别是那一个参数。

def my_fun(arg1, arg2):
    print(f"agr1: [ {arg1} ]")
    print(f"agr2: [ {arg2} ]")

my_fun("abc", "xyz")

执行输出:

agr1: [ abc ]
agr2: [ xyz ]

2. 可变参数

不同于参数数量固定的位置参数,在实际编程时,我们经常会遇到参数数量不固定的情形,尽管我们可以使用集合类型来封装数量不确定的参数,但是这样毕竟会多出一步封装集合的操作,比较繁琐,例如这个例子:

def sum(numbers):
    sum = 0
    for i in numbers:
        sum += i
    return sum

print(sum([1,2,3]))

针对这种参数数量不确定的情况,绝大多编程语言都会提供可变(不定长)参数特性,其实也就是简化了一小步操作:不需要调用方先封装一个集合,而是直接往参数列表中填参数即可:

def sum(*numbers):
    sum = 0
    for i in numbers:
        sum += i
    return sum

print(sum(1,2,3))

python中的可变参数是在参数名前加一个*,在方法体内该参数被定义为list或tuple类型,然后在调用时,直接输入多个数量不固定的参数,不必先封装成一个集合类型。

本质上,可变参数就是一个小语法糖。

3. 关键字参数

关键字参数可以视作可变参数的一个加强版:可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict(也就是一个map)。

关键字参数也是用于应对参数不固定的场景,但是它比可变参数更强的地方在于:它给出的参数除了值(value)本身,还额外附加了一个参数名作为key。请看出下示例:

def my_fun(arg1, arg2, **args):
    print(f"arg1={arg1}")
    print(f"arg2={arg2}")
    print(f"args={args}")

my_fun('a','b',arg3='c',arg4='d')

输出如下:

arg1=a
arg2=b
args={'arg3': 'c', 'arg4': 'd'}

关键字参数在参数名前加**(两个),在方法体内该参数被定义为dict类型,然后在调用时,直接输入多个数量不固定的参数=值的KV列表,不必先封装成一个dist类型。*

比较一下的话:可变参数省去了调用方封装list或tuple的操作,关键字参数省去了调用方封装dist的操作。

4. 命名关键字参数

关键字参数虽然灵活,但是调用方可以传入任意命名(key)的参数,有时候,如果函数设计者想要约束这种情况,只希望在给定的参数名集合内传入KV参数列表,此时就需要使用命名关键字参数了。依然是上述的例子,使用命名关键字参数是这样的:

def my_fun(arg1, arg2, *, arg3, arg4):
    print(f"arg1={arg1}")
    print(f"arg2={arg2}")
    print(f"arg3={arg3}")
    print(f"arg4={arg4}")

my_fun('a','b',arg3='c',arg4='d')

命名关键字参数的形式是在一系列关键字参数前使用一个*作为一个分隔符,*后面的参数被视为命名关键字参数。

但是命名关键字参数有非常不灵活的地方,它要求调用方必须传入完成的命名关键字参数列表,而不是一部分,以上面的例子为例:如果使用my_fun('a','b',arg3='c'),编译就会报错!提示:my_fun() missing 1 required keyword-only argument: 'arg4',虽然我们使用使用参数默认值语法来规避这个问题,但显然命名关键字参数已经和关键字参数有本质区别了,后者参数数量是不固定的,而前者已经固化了。

5. 多种参数形式组合

我们可以综合使用多种参数命令方式,但是要注意参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。例如,我们可以这样定义参数列表:

def my_fun(arg1, arg2, arg3='c', *args, **kw):
    print(f"arg1={arg1}")
    print(f"arg2={arg2}")
    print(f"arg3={arg3}")
    print(f"args={args}")
    print(f"kw={kw}")

my_fun('a','b','c','d', arg5='e')

输出如下:

arg1=a
arg2=b
arg3=c
args=('d',)
kw={'arg5': 'e'}

如果我们调整一下arg3='c', *args, **kw之间的先后顺序,函数就会报错,也就是前面说的,组合使用不同形式的参数时,顺序必须保持固定!

6. 代表任意可能参数的参数列表

在了解了多种参数形式组合之后,联想此前装饰器一节中,我们介绍的wrap函数,这个函数必须得能包裹任何函数,所以它的参数列表是要接受任何可能的参数组合,并委派给目标函数,这里就面临一个问题:在Python中代表任意可能参数的参数列表应该是什么样的?基于第5节的介绍,按照各种参数形式的出场顺序,我们可以想见:前任意多个位置参数+默认参数+可变参数组合落地的参数列表可以统一使用*args反向描述,后续可能存在的关键字参数,由于其独特的K=V书写样式,不能纳入*args,只能单独归类到**kw,这样归纳起来,实际上就能得到任意可能参数的参数列表:(*args, **kw)。在上一节介绍装饰器函数时就使用到了它:

def log(func):
    @functools.wraps(func)
    def wrap(*args, **kw):
        print('before call %s():' % func.__name__)
        result = func(*args, **kw)
        print('after call %s():' % func.__name__)
        return result
    return wrap