Python 中使用了 * 和 **

你会在很多地方看到 Python 中使用了 * 和 **。无论对于新手程序员还是从许多可能没有完全等效操作符的编程语言转移过来的人,这两个操作符可能有点神秘。我将讨论这些操作符是什么以及它们的使用方式。

* 与 ** 操作符的用法逐年增加,我将讨论目前所有可用的操作符方法。如果你在 Python 2 的时代学到 * 和 **,我建议至少略读这篇文章,因为 Python 3 为这些操作符添加了许多新用途。

我们将要讨论的东西

当我在这篇文章中讨论 * 和 ** 时,涉及的是 * 和 ** 前缀操作符,而不是中缀操作符。

所以我不是在谈论乘法和取幂:

2 * 5
2 ** 5

我们将要讨论的是 * 和 ** 前缀操作符,也就是在一个变量之前使用 * 和 ** 操作符。例如:

numbers = [2, 1, 3, 4, 7]
more_numbers = [*numbers, 11, 18]
print(*more_numbers, sep=', ')
2, 1, 3, 4, 7, 11, 18

上述代码中显示了 * 的两种用途,** 的用法还未展示。

* 和 ** 的用法包括:

  • 使用 * 和 ** 向函数传递参数

  • 使用 * 和 ** 捕捉传递至函数的参数

  • 使用 * 接受 keyword-only 参数

  • 使用 * 捕捉元组解包过程中的项

  • 使用 * 将可迭代对象解包至列表/元组

  • 使用 ** 将字典解包至其他字典

 

即使你认为你已经熟悉了所有这些使用 * 和 ** 的方法,我仍建议再看后文的每一个代码块,以确保它们是你熟悉的一切内容。Python 核心开发人员在过去几年里陆续向这些操作符添加了新功能,因此很容易忽略 * 和 ** 的一些新用法。

在函数调用中使用 * 解包参数

当调用函数时,* 操作符可用于将可迭代对象解包至函数调用的参数中:

>>> lst = [1, 2, 3, 4]
>>> print(lst[0], lst[1], lst[2], lst[3])
1 2 3 4
>>> print(*lst)
1 2 3 4

print(*fruit) 行将 fruits 列表中的所有项作为独立的参数传递到 print 函数调用中,甚至不需要知道列表中有多少个参数。

这里的 * 操作符不仅仅是语法糖。除非列表的长度是固定的,否则如果没有 *,就不可能将特定的可迭代对象中的所有项作为独立的参数传入函数。

** 操作符类似,但是使用关键字参数。** 操作符允许我们使用键-值对字典,并在函数调用中将其解包为关键字参数。

比如:

dict = {"a": "newA", "b":"newB","c":"newC"}
f = **dict
print(f)

>> a="newA", b="newB", c="newC"

 

 

>>> dic = {'a': 1, 'b': 2, 'c': 3}
>>> string = "{a}-{b}-{c}".format(**dic)
>>> string
'1-2-3'

根据我的经验,使用 ** 将关键字参数解包到函数调用中并不常见。我最常看到的是在实践实施继承时:对 super() 的调用通常包含 * 和 **

在函数调用中,可以多次使用 * 和 **

多次使用 * 有时会很方便:

>>> strlist = ['a', 'b', 'c', 'd']
>>> numbers = [2, 1, 3, 4, 7]
>>> print(*numbers, *strlist)
2 1 3 4 7 a b c d

多次使用 ** 类似:

 

>>> dic = {'a': 1, 'b': 2, 'c': 3}
>>> dic2 = {'e': "Python", 'f': 'everyday'}
>>> string = "{a}-{b}-{c}-{e}-{f}".format(
...     **dic,
...     **dic2,
... )
>>> string
'1-2-3-Python-everyday'

 

但是,在多次使用 ** 时需要小心。Python 中的函数不能多次指定相同的关键字参数,因此与 ** 一起使用的每个字典中的键必须是不同的,否则会引发异常。

 

*args和**kwargs的区别

*args和**kwargs主要用于函数定义。你可以将不定数量的参数传递给某个函数。

这里的不定的意思是:预先并不知道,函数使用者会传递多少个参数给你, 所以在这个场景下使用这两个关键字。

*args是用来发送一个非键值对的可变数量的参数列表给一个函数.

def function(x, y, *args):
    print(x, y, args)

function(1, 2, 3, 4, 5)

   输出结果:

                     

**kwargs 允许你将不定长度的键值对作为参数传递给一个函数。如果你想要在一个函数里处理带名字(关键字)的参数,你应该使用**kwargs。

def function(**kwargs):
    print(kwargs)

function(a=1, b=2, c=3)

    输出结果:

                  image

注意:当定义参数(形参)使用**kwargs时,传递的参数(实参)必须是解包过的字典,即带名字(关键字)的参数,

def f(**pars):
    for key,value in pars.items():
        print("key=%s; value=%s"%(key,value))



b={"b":"b","c":"c"}

f(**b)

>> key=c; value=c
      key=b; value=b

一句话总结:对于参数传递来说,***都是用来解包的,对于参数定义的时候,***都是用来打包的。两者应用在对于参数个数不确定的情况下。

 注意点:参数arg、*args、**kwargs三个参数的位置必须是一定的。必须是(arg,*args,**kwargs)这个顺序,否则程序会报错。

def function(arg,*args,**kwargs):
    print(arg,args,kwargs)

function(6,7,8,9,a=1, b=2, c=3)

    输出结果:

                 image

 

 

keyword-only 参数

在 Python 3 中,有一种特殊的语法来接受 keyword-only 参数。Keyword-only 参数是只能使用关键字语法指定的函数参数,这意味着它们不能使用位置指定参数。

为了接受 keyword-only 参数,在定义函数时,我们可以在 *args 参数后,**kargs 参数前加上命名参数:

 

def kwonly(*args, key, **kargs):    # key 即为 keyword-only 参数
    print(args, key, kargs)

 

>>> kwonly(1, 2, key=3, d=4, e=5)
(1, 2) 3 {'d': 4, 'e': 5}

如果试图使用位置参数指定 key,那么会引发一个错误:

 

>>> kwonly(1, 2, 3, d=4, e=5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: kwonly() missing 1 required keyword-only argument: 'key'

keyword-only 参数的特性很酷,但是如果你希望在不捕获无限位置参数的情况下需要 keyword-only 参数呢?

Python 允许这样做,但它的语法有点奇怪,仅使用一个 ** 后面的所有参数都将作为关键字传递:

 

def kwonly(a, *, b, c):
    print(a, b, c)

 

>>> kwonly(1, b=2, c=3)
1 2 3

因此不能不指定关键字:

 

>>> kwonly(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: kwonly() takes 1 positional argument but 3 were given

 

我通常在获取任意数量的位置参数时使用 keyword-only 参数,但我有时使用 * 来强制一个参数只通过位置指定。

Python 的内置 sorted 函数实际上使用了这种方法。如果你查看 sorted 的帮助信息,你将看到以下信息:

 

>>> help(sorted)
Help on built-in function sorted in module builtins:

sorted(iterable, /, *, key=None, reverse=False)
    Return a new list containing all items from the iterable in ascending order.

    A custom key function can be supplied to customize the sort order, and the
    reverse flag can be set to request the result in descending order.

 

在元组解包中使用 *

Python 3 还添加了一种使用 * 操作符的新方法,这种方法只与定义函数与调用函数时使用 * 的特性有关。

* 操作符现在可以用于元组解包:

 

>>> lst = [1, 2, 3, 4]
>>> a, b, *c = lst
>>> c
[3, 4]
>>> a, *c = lst
>>> c
[2, 3, 4]
>>> a, *b, c = lst
>>> b
[2, 3]

通常当我教授 * 的时候,我注意到你只能在一个多赋值调用中使用一个 * 表达式。这在技术上是不正确的,因为可以在嵌套解包中使用两个(这篇文章中讨论了嵌套解包):

 

lst = ['Python', 'Java', 'PHP', 'C']
((a, *b), *c) = lst
>>> a
'P'
>>> b
['y', 't', 'h', 'o', 'n']
>>> c
['Java', 'PHP', 'C']

 

我从来没见过它有什么用处,我不认为我会推荐使用它。

在列表常量中使用 *

Python 3.5 通过 PEP 448 引入了大量与 * 相关的新特性。最大的新特性之一是能够使用 * 将可迭代对象转储到新列表中。

假设你有一个函数,它取任意序列并返回一个序列和反转的序列合并的列表:

 

def revlist(seq):
    return list(seq) + list(reversed(seq))

这个函数需要多次将内容转换为列表,以便连接列表并返回结果。在 Python 3.5 中,我们可以这样输入:

 

def revlist(seq):
    return [*seq, *reversed(seq)]

 

这段代码删除了一些不必要的列表调用,因此我们的代码更高效,可读性更好。

这是另一个例子:

 

def rotate_first_item(seq):
    return [*seq[1:], seq[0]]

这个函数返回一个新列表,其中给定列表(或其他序列)中的第一项移动到新列表的末尾。

使用 * 操作符是将不同类型的可迭代对象连接在一起的好方法。* 操作符适用于任何可迭代对象,而 + 操作符只适用于所有类型都相同的特定序列。

这不仅仅局限于创建列表。我们还可以将可迭代对象转储到新的元组或集合中:

 

>>> lst = [1, 2, 3, 4]
>>> (*lst[1:], lst[0])
(2, 3, 4, 1)
>>> squared_list = (a ** 2 for a in lst)
>>> {*lst, *squared_list}
{1, 2, 3, 4, 9, 16}

在字典常量中使用 **

PEP 448 还扩展了 ** 的功能,允许该操作符将键/值对从一个字典转储到一个新字典中:

 

>>> dic = {'a': 1, 'b': 2, 'c': 3}
>>> dic2 = {'e': "Python", 'f': 'everyday'}
>>> all_dic = {**dic, **dic2}
>>> all_dic
{'a': 1, 'b': 2, 'c': 3, 'e': 'Python', 'f': 'everyday'}

这不仅可以用于合并两个字典,还可以复制一个字典,同时添加一个新的值:

 

>>> add_dic = {**dic, 'g': 4}
>>> add_dic
{'a': 1, 'b': 2, 'c': 3, 'g': 4}

 

或复制/合并字典,同时重写特定的值:

 

>>> new_dic = {**dic, 'a': 111}
>>> new_dic
{'a': 111, 'b': 2, 'c': 3}

 

Python 的 * 和 ** 操作符不仅仅是语法糖。它们允许你做的一些事情可以通过其他方式实现,但是替代 * 和 ** 的方法往往更麻烦,且需要消耗更多资源。而且没有它们提供的一些特性,一些操作不可能实现:例如,没有 * 的函数无法接受任意数量的位置参数。

如果你不懂 * 和 **,或者你想记住它们的所有用法,不要这样做!这些操作符有很多用途,记住每种操作符的具体用法并不重要,重要的是了解你何时能够使用这些操作符。我建议使用本文作为速查表,或者制作你自己的速查表,以帮助你在 Python 中使用 * 和 **

(完)

 

看完本文有收获?请转发分享给更多人

关注「Python那些事」,做全栈开发工程师

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值