Python:*args 和 **kwargs

其实并不是必须写成*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的所有元素作为可变参数传进去。这种写法相当有用,而且很常见。 

命名关键字参数

关键字参数可以传入任意的关键字参数,调用者可以传入不受限制的关键字参数。如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收cityjob作为关键字参数,其他,不接收。这种方式定义的函数如下:

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

由于调用时缺少参数名cityjob,Python解释器把这4个参数均视为位置参数,但person()函数仅接受2个位置参数,因为定义的就是关键字参数,只能通过关键字参数调用。

命名关键字参数可以有缺省值(默认参数),从而简化调用。

可变参数无法和命名关键字参数混合。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数/命名关键字参数和关键字参数。

猜你喜欢

转载自blog.csdn.net/answer3lin/article/details/86368757
今日推荐