Python不定长参数 (*args、**kwargs含义),附使用范例
本片文章主要整合15. 函数和装饰器和python的可变参数和关键字参数(*args **kw)两篇文章,并做对比总结,以及添加综合案例。主要目标是熟悉 python 中函数传参种类和方法,并掌握不同传参的优缺点和联合传参的注意事项。
本文难免有不足之处,若对该主题感兴趣,如下的几个参考文献可能帮助到你
概要
五种函数参数类型对比表
种类 | 函数定义 | 参数传递 | 功能 | 参考 |
---|---|---|---|---|
POSITIONAL_ONLY 位置参数 | ord(c, /) | 一些内置函数,只支持位置传入 | 1.1 POSITIONAL_ONLY 位置参数 | |
POSITIONAL_OR_KEYWORD 位置或关键字参数 | def foo(n): | 自定义函数支持位置和关键字传入,这种类型最常见 | 基础传入方式,最常用 | |
VAR_POSITIONAL 可变长参数 | def variable_args(name="default", *args): | 1. 传入列表 list 2. 传入元组 tuple 3. 传入逐个传入元素 |
降低变长数组和元组传入的复杂度 | 2.1 VAR_POSITIONAL 可变长参数 参数传递 |
KEYWORD_ONLY 关键字参数 | def person(name, age, *, city, job): | 1. city 和 job 通过关键字传入 | 限制输入的关键字参数 | 2.3命名关键字参数 |
VAR_KEYWORD 可变关键字参数 | def person(name, age, **kw) | 1. 关键字传入 2. 传入字典 dict |
任意不受限,但是函数内部要增加检查 | 2.3VAR_KEYWORD 可变关键字参数 参数传递 |
接受任意多个参数的函数定义方法
定义一个可以处理任意类型任意参数数目的函数。
def test_args(*args, **kwargs):
print(args)
print(kwargs)
test_args(1, 2, {"key0": "val0"}, name="name", age=18)
>>>
(1, 2, {'key0': 'val0'})
{'name': 'name', 'age': 18}
test_args() 是一个可以接受任意多个参数的函数。由于参数处理是有优先级的,kwargs 和 args 顺序不可颠倒。
混合定义时注意事项
- 参数定义的顺序必须是:必选参数、默认参数、可变参数/命名关键字参数和关键字参数。
def f1(a, b, c=0, *args, **kw):
print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
def f2(a, b, c=0, *, d, **kw):
print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
>>> f1(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>>> f1(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
>>> f1(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> f1(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
>>> f2(1, 2, d=99, ext=None)
a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}
>>> args = (1, 2, 3, 4)
>>> kw = {'d': 99, 'x': '#'}
>>> f1(*args, **kw)
a = 1 b = 2 c = 3 args = () kw = {'d': 99, 'x': '#'}
>>> args = (1, 2, 3)
>>> kw = {'d': 88, 'x': '#'}
>>> f2(*args, **kw)
a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}
1 函数参数类型
Python 中的函数参数类型一共有五种,参考 inspect 模块 ,分别是:
- POSITIONAL_ONLY 位置参数,内置函数或模块使用,用户无法自定义一个只支持位置参数的函数。
- POSITIONAL_OR_KEYWORD 位置或关键字参数,参数同时支持位置或者关键字传递给函数。
- VAR_POSITIONAL 可变长参数,任意多个位置参数通过元组传递给函数。
- KEYWORD_ONLY 关键字参数,也被称为命名参数,通过指定的键值对传递给函数。
- VAR_KEYWORD 可变关键字参数,任意多个键值对参数通过字典传递给函数。
1.1 POSITIONAL_ONLY 位置参数
一些内置函数,无法通过名称来传递,否则会报不支持关键参数的错误,比如内置函数 oct(x),ord(c),divmod(x, y)等等。它们的函数手册里一般就使用一个字母来表示一个参数,常用的比如 x,y,c。
ord(c, /)
Return the Unicode code point for a one-character string.
ord(c='1')
>>>
ord(c='1')
TypeError: ord() takes no keyword arguments
1.2 POSITIONAL_OR_KEYWORD 位置或关键字参数
def foo(n):
print(n)
foo(1)
foo(n = 2)
>>>
1
2
自定义的函数 foo(),不仅可以通过第一个参数位置来传递实参 1,还可以通过名称 n 来传递参数 2。这里的 n 就是一个位置或关键字参数。它是最常用的参数传递方式。
1.3 VAR_POSITIONAL 可变长参数
可变参数用一个 * 号来声明,它把所有接收到的,未被位置或关键字参数处理的参数放入一个元组。
def variable_args(name="default", *args):
print("name: %s" % name)
print(args)
variable_args("John", "Teacher", {"Level": 1})
>>>
name: john
('Teacher', {'Level': 1})
可以看到,”John” 均通过参数位置传递给了形参 name,后边多余的参数全部传递给了 *args,它是一个元组。注意键值对参数不能被它处理。
1.4 KEYWORD_ONLY 关键字参数
def keyword_only_args(name="default", *args, age):
print("name: %s, age: %d" % (name, age))
print(args)
keyword_only_args("John", "Teacher", {"Level": 1}, age=30)
>>>
name: John, age: 30
('Teacher', {'Level': 1})
由于 age 形参位于可变参数之后,那么它的位置是不明确的,此时只能指定关键字 age,以键值对的方式传递它,被称为关键字参数。此时 args 元组中不会处理它。
命名关键字参数 是【KEYWORD_ONLY 关键字参数】的变种,详细讨论见 2.3命名关键字参数
1.5 VAR_KEYWORD 可变关键字参数
可变关键字参数通过前缀 ** 来声明,这种参数类型可以接收 0 个或多个键值对参数,并存入一个字典。
def keyword_variable_args(name="default", *args, age, **kwargs):
print("name: %s, age: %d" % (name, age))
print(args)
print(kwargs)
keyword_variable_args("John", "Teacher", {"Level": 1}, id="332211",
city="New York", age=30)
>>>
name: John, age: 30
('Teacher', {'Level': 1})
{'id': '332211', 'city': 'New York'}
通过以上的示例,我们看到参数处理是有优先级的,首先通过位置匹配,然后进行关键字匹配,最后剩下的所有参数按照是否提供参数名来对应到可变参数或可变关键字参数。
2. 函数参数传递形式
在介绍了 Python 参数类型后,我们可以通过两种形式为形参提供实参。
def test_input_args(list0, num0, name="Tom"):
print("list:%s, num:%d, name:%s" % (str(list0), num0, name))
test_input_args([1], 2, name="John")
test_input_args(*([1], 2), **{"name": "John"})
>>>
list:[1], num:2, name:John
list:[1], num:2, name:John
可以通过常用位置和关键字传递,也可以使用可变参数和可变关键字参数传递,它们是等价的。有了第二种参数传递形式,就可以在一个函数中调用不同的函数了,这一特性对于实现装饰器函数非常重要。
def func0(n):
print("from %s, %d" %(func0.__name__, n))
def func1(m, n):
print("from %s, %d" %(func0.__name__, m + n))
def test_call_func(func, *args, **kwargs):
func(*args, **kwargs)
test_call_func(func0, 1)
test_call_func(func1, 1, 2)
>>>
from func0, 1
from func0, 3
2.1 VAR_POSITIONAL 可变长参数 参数传递
# 定义函数
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
# 调用函数 - 可以传入任意个参数,包括0个参数
calc(1, 2)
calc()
# 调用函数 - 已经有一个 list 或者 tuple,在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去
nums = [1, 2, 3]
calc(*nums) # 传入的是nums。在函数内部会转化为 tuple 来操作
可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple
2.2 VAR_KEYWORD 可变关键字参数 参数传递
# 定义函数
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
# 传入参数 传入关键字
>>> person('Michael', 30)
name: Michael age: 30 other: {}
>>> person('Bob', 35, city='Beijing')
name: Bob age: 35 other: {'city': 'Beijing'}
>>> person('Adam', 45, gender='M', job='Engineer')
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}
# 传入参数 已经有一个 dict
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
# 注意kw获得的dict是extra的一份拷贝,对kw的改动不会影响到函数外的extra
关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。
2.3 命名关键字参数 参数传递
对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kw检查。
仍以person()函数为例,我们希望检查是否有city和job参数:
def person(name, age, **kw):
if 'city' in kw:
# 有city参数
pass
if 'job' in kw:
# 有job参数
pass
print('name:', name, 'age:', age, 'other:', kw)
函数内部检查关键字参数和变长参数数目,以匹配不同处理分支
def test_var_args(*args):
if len(args) == 2:
print(args[0]+args[1])
else:
print(args[0])
test_var_args(1,2)
test_var_args(1)
def test_var_kwargs(**kwargs):
#定义函数知道key:one two
if len(kwargs) == 2:
print(kwargs['one']+kwargs['two'])
else:
print(kwargs['one'])
test_var_kwargs(one = 1, two = 2)
test_var_kwargs(one = 1)
如果要限制关键字参数的名字,就可以用命名关键字参数,和关键字参数**kw不同,命名关键字参数需要一个特殊分隔符,后面的参数被视为命名关键字参数。因为加 * 后,后面参数无法通过位置传入参数,为关键字参数。例如,只接收city和job作为关键字参数。这种方式定义的函数如下:
# 定义函数
def person(name, age, *, city, job):
print(name, age, city, job)
# 传入参数
>>> person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer
# 如果没有传入参数名,调用将报错
>>> person('Jack', 24, 'Beijing', 'Engineer')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: person() takes 2 positional arguments but 4 were given
# 命名关键字带有默认值,可以简化调用
def person(name, age, *, city='Beijing', job):
print(name, age, city, job)
# 函数调用
>>> person('Jack', 24, job='Engineer')
Jack 24 Beijing Engineer