其实并不是必须写成*args
和**kwargs
。 只有变量前面的 *
(星号)才是必须的。 你也可以写成*var
和**vars
. 而写成*args
和**kwargs
只是一个通俗的命名约定。
可变参数
(1)可变参数可以通过默认参数实现。先位置参数,默认参数,收集位置参数,收集关键字参数(定义和调用都应遵循)。默认参数必须指向不变对象。好处:极大降低调用复杂度。
定义一个有可选参数的函数是非常简单的,直接在函数定义中给参数指定一个默认值,并放到参数列表最后就行了。
首先看python内建函数:
print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)
def spam(a, b=42):
print(a, b)
spam(1) # Ok. a=1, b=42
spam(1, 2) # Ok. a=1, b=2
如果默认参数是一个可修改的容器比如一个列表、集合或者字典,可以使用None作为默认值,就像下面这样:
# Using a list as a default value
def spam(a, b=None):
if b is None:
b = []
...
如果你并不想提供一个默认值,而是想仅仅测试下某个默认参数是不是有传递进来,可以像下面这样写:
_no_value = object()
def spam(a, b=_no_value):
if b is _no_value:
print('No b value supplied')
...
默认参数的值仅仅在函数定义的时候赋值一次。
>> x = 42
>>> def spam(a, b=x):
... print(a, b)
...
>>> spam(1)
1 42
>>> x = 23 # Has no effect
>>> spam(1)
1 42
>>>
其次,默认参数的值应该是不可变的对象,比如None、True、False、数字或字符串。 特别的,千万不要像下面这样写代码:
def spam(a, b=[]): # NO!
...
因为b是可变的列表。为了避免这种情况的发生,最好是将默认值设为None, 然后在函数里面检查它,前面的例子就是这样做的。在测试None值时使用 is
操作符是很重要的,也是这种方案的关键点。 有时候大家会犯下下面这样的错误:
def spam(a, b=None):
if not b: # NO! Use 'b is None' instead
b = []
...
这么写的问题在于尽管None值确实是被当成False, 但是还有其他的对象(比如长度为0的字符串、列表、元组、字典等)都会被当做False。 因此,上面的代码会误将一些其他输入也当成是没有输入。
一个函数需要测试某个可选参数是否被使用者传递进来。 这时候需要小心的是你不能用某个默认值比如None、 0或者False值来测试用户提供的值(因为这些值都是合法的值,是可能被用户传递进来的)。为了解决这个问题,你可以创建一个独一无二的私有对象实例,就像上面的_no_value变量那样。 在函数里面,你可以通过检查被传递参数值跟这个实例是否一样来判断。 这里的思路是用户不可能去传递这个_no_value实例作为输入。 因此,这里通过检查这个值就能确定某个参数是否被传递进来了。
这里对 object()
的使用看上去有点不太常见。object
是python中所有类的基类。 你可以创建 object
类的实例,但是这些实例没什么实际用处,因为它并没有任何有用的方法, 也没有任何实例数据(因为它没有任何的实例字典,你甚至都不能设置任何属性值)。 你唯一能做的就是测试同一性。这个刚好符合我的要求,因为我在函数中就只是需要一个同一性的测试而已。
函数参数:必选参数、默认参数、可选参数、关键字参数
可选参数是*args,关键字参数是**kwargs。默认参数最好放在必选参数之后,因为关键字参数必须在位置参数之后。
函数调用的参数顺序问题
1、如果所有参数都有形参名(关键字参数),不管顺序,按照形参名指定实参。
2、如果都没有形参名(位置参数),则按照顺序用实参对形参赋值。
3、如果有的参数有形参名,而有的参数没有形参名(既有关键字参数又有位置参数)。
(1)要求关键字参数在位置参数之后1。即使顺序是对的也不行2。
SyntaxError: non-keyword arg after keyword arg
(2)如果符合了(1),前面的位置参数会按照顺序赋值给形参,所以后面不能再通过关键字参数进行赋值,否则会出现多变量重复赋值问题3。
TypeError: fuc() got multiple values for keyword argument 'a'
(3)如果是前面按照顺序进行赋值,后面利用关键字参数进行赋值,则正确4。
def fuc(a, b):
print(a)
print(b)
if __name__ == '__main__':
fuc(1,2) # 第一种情况,都是位置参数
fuc(b=1, a=2) # 第二种情况,都是关键字参数
fuc(a=2, 1) # 第三种情况 SyntaxError: non-keyword arg after keyword arg
fuc(b=2, 1) # 第三种情况 SyntaxError: non-keyword arg after keyword arg
fuc(1, a=2) # 第三种情况 TypeError: fuc() got multiple values for keyword argument 'a'
fuc(1, b=2) # 第三种情况 对的
(2)*args是可变的positional arguments列表(tuple),**kwargs是可变的keyword arguments列表(dict)。*args
和 **kwargs
主要用于函数定义。 你可以将不定数量的参数传递给一个函数。类似于java里的可变参数(...)。
这里的不定的意思是:预先并不知道, 函数使用者会传递多少个参数给你, 所以在这个场景下使用这两个关键字。
*args
*args
是用来发送一个非键值对的可变数量的参数列表给一个函数.
例如:
def test_var_args(f_arg, *argv):
print("first normal arg:", f_arg)
for arg in argv:
print("another arg through *argv:", arg)
test_var_args('yasoob', 'python', 'eggs', 'test')
这会产生如下输出:
first normal arg: yasoob
another arg through *argv: python
another arg through *argv: eggs
another arg through *argv: test
**kwargs
**kwargs
允许你将不定长度的键值对, 作为参数传递给一个函数。 如果你想要在一个函数里处理带名字的参数, 你应该使用**kwargs
。比如:a=10。
这里有个让你上手的例子:
def greet_me(**kwargs):
for key, value in kwargs.items():
print("{0} == {1}".format(key, value))
>>> greet_me(name="yasoob")
name == yasoob
现在你可以看出我们怎样在一个函数里, 处理了一个键值对参数了。
这就是**kwargs
的基础, 而且你可以看出它有多么有用。 接下来让我们谈谈,你怎样使用*args
和 **kwargs
来调用一个参数为列表或者字典的函数。
使用 *args
和 **kwargs
来调用函数
那现在我们将看到怎样使用*args
和**kwargs
来调用一个函数。 假设,你有这样一个小函数:
def test_args_kwargs(arg1, arg2, arg3):
print("arg1:", arg1)
print("arg2:", arg2)
print("arg3:", arg3)
你可以使用*args
或**kwargs
来给这个小函数传递参数。 下面是怎样做:
# 首先使用 *args
>>> args = ("two", 3, 5)
>>> test_args_kwargs(*args)
arg1: two
arg2: 3
arg3: 5
# 现在使用 **kwargs:
>>> kwargs = {"arg3": 3, "arg2": "two", "arg1": 5}
>>> test_args_kwargs(**kwargs)
arg1: 5
arg2: two
arg3: 3
当使用**kawargs时,默认值时value。
标准参数与*args、**kwargs
在使用时的顺序
那么如果你想在函数里同时使用所有这三种参数, 顺序是这样的:
some_func(fargs, *args, **kwargs)
什么时候使用它们?
依据需求而定。最常见的用例是在写函数装饰器的时候。
此外它也可以用来做猴子补丁(monkey patching)。猴子补丁的意思是在程序运行时(runtime)修改某些代码。 打个比方,你有一个类,里面有个叫get_info
的函数会调用一个API并返回相应的数据。如果我们想测试它,可以把API调用替换成一些测试数据。例如:
import someclass
def get_info(self, *args):
return "Test data"
someclass.get_info = get_info
可变参数既可以直接传入:func(1, 2, 3)
,又可以先组装list或tuple,再通过*args
传入:func(*(1, 2, 3))
;
关键字参数既可以直接传入:func(a=1, b=2)
,又可以先组装dict,再通过**kw
传入:func(**{'a': 1, 'b': 2})
。
最后给出各种参数的定义:
必选参数、默认参数、可变参数、命名关键字参数、关键字参数
参数定义的顺序必须是:必选参数–>默认参数–>可变参数–>命名关键字参数–>关键字参数
必选参数
必须传入参数的参数(可以认为就是位置参数)。非默认参数和可变参数。
位置参数
调用函数时根据函数定义的参数位置来传递参数。位置参数就是按照顺序给出赋值。
有位置参数时,位置参数必须在关键字参数的前面,但关键字参数之间不存在先后顺序的。
关键字参数
传入参数时带上参数名。用于函数调用,通过“键-值”形式加以指定。可以让函数更加清晰、容易使用,同时也清除了参数的顺序需求。可以扩展函数的功能。
注意kw获得的dict是extra(外部变量)的一份拷贝,对kw的改动不会影响到函数外的extra。
默认参数
就是在写函数的时候直接给参数传默认的值,调用的时候,默认参数已经有值,就不用再传值了。作用:最大的好处就是降低调用函数的难度。用于定义函数,为参数提供默认值,调用函数时可传可不传该默认参数的值(注意:所有位置参数必须出现在默认参数前,包括函数定义和调用,因为需要先给位置参数进行赋值。除非用关键字参数进行限制)。
注意点:
第一:必选参数在前,默认参数在后,否则python解释器会报错。
第二:默认参数一定要指向不变对象!指向不变对象!指向不变对象!
(注意:python中的字符串,数字,元组都可以看做对象。)
为什么要设计str、None这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。
可变参数(位置参数)
定义:可变参数就是传入的参数个数是可变的,可以是0个,1个,2个,……很多个。传进的所有参数都会被args变量收集,它会根据传进参数的位置合并为一个元组(tuple),args是元组类型,这就是包裹位置传递。
作用:就是可以一次给函数传很多的参数。特征:*args。相当于可变位置参数。
可变关键字参数
可变的带参数名的参数。可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。
而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。在调用函数时,可以只传入必选参数:作用:扩展函数的功能。特征:**kw。
可变也叫也叫包裹(packing)。
解包裹参数
*和**,也可以在函数调用的时候使用,称之为解包裹(unpacking)。
def print_hello(name, sex):
print name, sex
# args = ('tanggu', '男')
# print_hello(*args)
# tanggu 男
*args
表示把args
这个list的所有元素作为可变参数传进去。这种写法相当有用,而且很常见。
命名关键字参数
关键字参数可以传入任意的关键字参数,调用者可以传入不受限制的关键字参数。如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city
和job
作为关键字参数,其他,不接收。这种方式定义的函数如下:
def person(name, age, *, city, job):
print(name, age, city, job)
作用:限制要传入的参数的名字,只能传我已命名的关键字参数。
特征:和关键字参数**kw
不同,命名关键字参数需要一个特殊分隔符,后面的参数被视为命名关键字参数。命名关键字参数需要一个特殊分隔符*,而后面的参数被视为命名关键字参数。
>>> person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer
如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:
def person(name, age, *args, city, job):
print(name, age, args, city, job)
命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:
>>> 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
由于调用时缺少参数名city
和job
,Python解释器把这4个参数均视为位置参数,但person()
函数仅接受2个位置参数,因为定义的就是关键字参数,只能通过关键字参数调用。
命名关键字参数可以有缺省值(默认参数),从而简化调用。
可变参数无法和命名关键字参数混合。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数/命名关键字参数和关键字参数。
传递实参
- 位置实参:按照顺序
- 关键字实参:键值对,准确指定函数定义中的形参名。
- 默认值:在形参列表中先列出没有默认值的形参,再列出有默认值的形参。以让Python正确解读位置实参。