Python中的星号本质及其使用方法详解


翻译:Python 开发者 - 一汀, 英文:Trey Hunner程序员

http://blog.jobbole.com/114655/编程

Python开发者网络

在 Python 中有不少地方能够看到***。在某些情形下,不管是对于新手程序员,仍是从其余不少没有彻底相同操做符的编程语言迁移过来的人来讲,这两个操做符均可能有点神秘。所以,我想讨论一下这些操做符的本质及其使用方式。dom

多年以来,***操做符的功能不断加强。在本文中,我将讨论目前这些操做符全部的使用方法,并指出哪些使用方法只能在目前的 Python 版本中应用。所以,若是你学习过 Python 2 中***的使用方法,那么我建议你至少浏览一下本文,由于 Python 3 中添加了许多***的新用途。编程语言

若是你是新接触 Python 不久,还不熟悉关键字参数(亦称为命名参数),我建议你首先阅读我有关Python中的关键字参数的文章。函数


一、不属于咱们讨论范围的内容

在本文中, 当我讨论***时,我指的是*** 学习

前缀
操做符,而不是
中缀
操做符。

也就是说,我讲述的不是乘法和指数运算:ui

>>> 2 * 5翻译

10code

>>> 2 ** 5

32



二、那么咱们在讨论什么内容呢?

咱们讨论的是***前缀运算符,即在变量前使用的***运算符。例如:

>>> numbers = [2, 1, 3, 4, 7]

>>> more_numbers = [*numbers, 11, 18]

>>> print(*more_numbers, sep=', ')

2, 1, 3, 4, 7, 11, 18

上述代码中展现了*的两种用法,没有展现**的用法。

这其中包括:

  1. 使用***向函数传递参数

  2. 使用***捕获被传递到函数中的参数

  3. 使用*接受只包含关键字的参数

  4. 使用*在元组解包时捕获项

  5. 使用*将迭代项解压到列表/元组中

  6. 使用**将字典解压到其余字典中

即便你认为本身已经熟悉***的全部使用方法,我仍是建议你查看下面的每一个代码块,以确保都是你熟悉的内容。在过去的几年里,Python 核心开发人员不断地为这些操做符添加新的功能,对于使用者来讲很容易忽略***‘的一些新用法。


三、星号用于将可迭代对象拆分并分别做为函数参数

当调用函数时,*运算符可用于将一个迭代项解压缩到函数调用中的参数中:

>>> fruits = ['lemon', 'pear', 'watermelon', 'tomato']

>>> print(fruits[0], fruits[1], fruits[2], fruits[3])

lemon pear watermelon tomato

>>> print(*fruits)

lemon pear watermelon tomato

print(*fruits)代码行将fruits列表中的全部项做为独立的参数传递给print函数调用,甚至不须要咱们知道列表中有多少个参数。

*运算符在这里远不止是语法糖而已。要想用一个特定的迭代器将全部项做为独立的参数传输,若不使用*是不可能作到的,除非列表的长度是固定的。

下面是另外一个例子:

def transpose_list(list_of_lists):

return [

list(row)

for row in zip(*list_of_lists)

]

这里咱们接受一个二维列表并返回一个“转置”的二维列表。

>>> transpose_list([[1, 4, 7], [2, 5, 8], [3, 6, 9]])

[[1, 2, 3], [4, 5, 6], [7, 8, 9]]


**操做符完成了相似的操做,只不过使用了关键字参数。**运算符容许咱们获取键-值对字典,并在函数调用中将其解压为关键字参数。

>>> date_info = {'year': "2020", 'month': "01", 'day': "01"}

>>> filename = "{year}-{month}-{day}.txt".format(**date_info)

>>> filename '2020-01-01.txt' `

根据个人经验,使用**将关键字参数解压缩到函数调用中并不常见。我最常看到它的地方是在实现继承时:对uper()的调用一般包括***

如 Python 3.5 那样,在函数调用中,***均可以被屡次使用。

有时,屡次使用*会很方便:

>>> fruits = ['lemon', 'pear', 'watermelon', 'tomato']

>>> numbers = [2, 1, 3, 4, 7]

>>> print(*numbers, *fruits)

2 1 3 4 7 lemon pear watermelon tomato `


屡次使用**也能够达到类似的效果:

>>> date_info = {'year': "2020", 'month': "01", 'day': "01"}

>>> track_info = {'artist': "Beethoven", 'title': 'Symphony No 5'}

>>> filename = "{year}-{month}-{day}-{artist}-{title}.txt".format(

... **date_info,

... **track_info,

... )

>>> filename

'2020-01-01-Beethoven-Symphony No 5.txt'

不过,在屡次使用**时须要特别当心。Python 中的函数不能屡次指定相同的关键字参数,所以在每一个字典中与**一块儿使用的键必须可以相互区分,不然会引起异常。


四、星号用于压缩被传递到函数中的参数

在定义函数时,*运算符可用于捕获传递给函数的位置参数。位置参数的数量不受限制,捕获后被存储在一个元组中。

from random import randint

def roll(*dice):

return sum(randint(1, die) for die in dice)


这个函数接受的参数数量不受限制:

>>> roll(20)

18

>>> roll(6, 6)

9

>>> roll(6, 6, 6)

8

Python 的printzip函数接受的位置参数数量不受限制。*的这种参数压缩用法,容许咱们建立像printzip同样的函数,接受任意数量的参数。

**运算符也有另一个功能:咱们在定义函数时,可使用** 捕获传进函数的任何关键字参数到一个字典当中:

def tag(tag_name, **attributes):

attribute_list = [

f'{name}="{value}"'

for name, value in attributes.items()

]

return f"<{tag_name} {' '.join(attribute_list)}>"

** 将捕获咱们传入这个函数中的任何关键字参数,并将其放入一个字典中,该字典将引用attributes参数。

>>> tag('a', href="http://treyhunner.com")

'<a href="http://treyhunner.com">'

>>> tag('img', height=20, width=40, src="face.jpg")

'<img height="20" width="40" src="face.jpg">'



五、只有关键字参数的位置参数

在 Python 3 中,咱们如今拥有了一种特殊的语法来接受只有关键字的函数参数。只有关键字的参数是

只能
使用关键字语法来指定的函数参数,也就意味着不能按照位置来指定它们。

在定义函数时,为了接受只有关键字的参数,咱们能够将命名参数放在*后:

def get_multiple(*keys, dictionary, default=None):

return [

dictionary.get(key, default)

for key in keys

]

上面的函数能够像这样使用:

>>> fruits = {'lemon': 'yellow', 'orange': 'orange', 'tomato': 'red'}

>>> get_multiple('lemon', 'tomato', 'squash', dictionary=fruits,default='unknown')

['yellow', 'red', 'unknown']

参数dictionarydefault*keys后面,这意味着它们

只能
被指定为关键字参数。若是咱们试图按照位置来指定它们,咱们会获得一个报错:

>>> fruits = {'lemon': 'yellow', 'orange': 'orange', 'tomato': 'red'}

>>> get_multiple('lemon', 'tomato', 'squash', fruits, 'unknown')

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

TypeError: get_multiple() missing 1 required keyword-only argument: 'dictionary'

这种行为是经过 PEP 3102 被引入到 Python 中的。


六、没有位置参数关键字的参数

只使用关键字参数的特性很酷,可是若是您但愿只使用关键字参数而不捕获无限的位置参数呢?

Python 使用一种有点奇怪的 单独* 语法来实现:

def with_previous(iterable, *, fillvalue=None):

"""Yield each iterable item along with the item before it."""

previous = fillvalue

for item in iterable:

yield previous, item

previous = item

这个函数接受一个迭代器参数,能够按照位置或名字来指定此参数(做为第一个参数),以及关键字参数fillvalue,这个填充值参数只使用关键字。这意味着咱们能够像下面这样调用 with_previous:

>>> list(with_previous([2, 1, 3], fillvalue=0))

[(0, 2), (2, 1), (1, 3)]


但像这样就不能够:

>>> list(with_previous([2, 1, 3], 0))

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

TypeError: with_previous() takes 1 positional argument but 2 were given `

这个函数接受两个参数,其中fillvalue参数

必须被指定为关键字参数

我一般在获取任意数量的位置参数时只使用关键字参数,但我有时使用这个*强制按照位置指定一个参数。

实际上,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 inascending 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.

sorted的官方说明中,有一个单独的*参数。


七、星号用于元组拆包

Python 3 还新添了一种 * 运算符的使用方式,它只与上面定义函数时和调用函数时*的使用方式相关。

如今,*操做符也能够用于元组拆包:

>>> fruits = ['lemon', 'pear', 'watermelon', 'tomato']

>>> first, second, *remaining = fruits

>>> remaining

['watermelon', 'tomato']

>>> first, *remaining = fruits

>>> remaining

['pear', 'watermelon', 'tomato']

>>> first, *middle, last = fruits

>>> middle

['pear', 'watermelon']

若是你想知道什么状况下能够在你本身的代码中使用它,请查看我关于 Python 中的 tuple 解包 文章中的示例。在那篇文章中,我将展现如何使用*操做符做为序列切片的替代方法。

一般当我教*的时候,我告诉你们只能在多重赋值语句中使用一个*表达式。实际来讲这是不正确的,由于能够在嵌套解包中使用两个*(我在元组解包文章中讨论了嵌套解包):

>>> fruits = ['lemon', 'pear', 'watermelon', 'tomato']

>>> first, second, *remaining = fruits

>>> remaining

['watermelon', 'tomato']

>>> first, *remaining = fruits

>>> remaining

['pear', 'watermelon', 'tomato']

>>> first, *middle, last = fruits

>>> middle

['pear', 'watermelon']

可是,我历来没见过它有什么实际用处,即便你由于它看起来有点神秘而去寻找一个例子,我也并不推荐这种使用方式。

将此添加到 Python 3.0 中的 PEP 是 PEP 3132,其篇幅不是很长。


八、列表文字中的星号

Python 3.5 经过 PEP 448 引入了大量与*相关的新特性。其中最大的新特性之一是可以使用*将迭代器转储到新列表中。

假设你有一个函数,它以任一序列做为输入,返回一个列表,其中该序列和序列的倒序链接在了一块儿:

def palindromify(sequence):

return list(sequence) + list(reversed(sequence))

此函数须要屡次将序列转换为列表,以便链接列表并返回结果。在 Python 3.5 中,咱们能够这样编写函数:

def palindromify(sequence):

return [*sequence, *reversed(sequence)]

这段代码避免了一些没必要要的列表调用,所以咱们的代码更高效,可读性更好。

下面是另外一个例子:

def rotate_first_item(sequence):

return [*sequence[1:], sequence[0]]

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

* 运算符的这种使用是将不一样类型的迭代器链接在一块儿的好方法。* 运算符适用于链接任何种类的迭代器,然而 + 运算符只适用于类型都相同的特定序列。

除了建立列表存储迭代器之外,咱们还能够将迭代器转储到新的元组或集合中:

>>> fruits = ['lemon', 'pear', 'watermelon', 'tomato']

>>> (*fruits[1:], fruits[0])

('pear', 'watermelon', 'tomato', 'lemon')

>>> uppercase_fruits = (f.upper() for f in fruits)

>>> {*fruits, *uppercase_fruits}

{'lemon', 'watermelon', 'TOMATO', 'LEMON', 'PEAR','WATERMELON', 'tomato', 'pear'}

注意,上面的最后一行使用了一个列表和一个生成器,并将它们转储到一个新的集合中。在此以前,并无一种简单的方法能够在一行代码中完成这项工做。曾经有一种方法能够作到这一点,但是并不容易被记住或发现:


九、两个星号用于字典文本

PEP 448 还经过容许将键/值对从一个字典转储到一个新字典扩展了**操做符的功能:

>>> date_info = {'year': "2020", 'month': "01", 'day': "01"}

>>> track_info = {'artist': "Beethoven", 'title': 'Symphony No 5'}

>>> all_info = {**date_info, **track_info}

>>> all_info

{'year': '2020', 'month': '01', 'day': '01', 'artist': 'Beethoven', 'title':'Symphony No 5'}

我还写了另外一篇文章:在Python中合并字典的惯用方法。

不过,**操做符不只仅能够用于合并两个字典。

例如,咱们能够在复制一个字典的同时添加一个新值:

>>> date_info = {'year': '2020', 'month': '01', 'day': '7'}

>>> event_info = {**date_info, 'group': "Python Meetup"}

>>> event_info

{'year': '2020', 'month': '01', 'day': '7', 'group': 'Python Meetup'}

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

>>> event_info = {'year': '2020', 'month': '01', 'day': '7', 'group':'Python Meetup'}

>>> new_info = {**event_info, 'day': "14"}

>>> new_info

{'year': '2020', 'month': '01', 'day': '14', 'group': 'Python Meetup'}



十、Python 的星号很是强大

Python 的 *** 运算符不只仅是语法糖。 *** 运算符容许的某些操做能够经过其余方式实现,可是每每更麻烦和更耗费资源。并且 *** 运算符提供的某些特性没有替代方法实现:例如,函数在不使用 * 时就没法接受任意数量的位置参数。

在阅读了*** 运算符的全部特性以后,您可能想知道这些奇怪操做符的名称。不幸的是,它们的名字并不简练。我据说过* 被称为“打包”和“拆包“运算符。我还据说过其被称为“splat”(来自 Ruby 世界),也据说过被简单地称为“star”。

我倾向于称这些操做符为“星”和“双星”或“星星”。这种叫法并不能区分它们和它们的中缀关系(乘法和指数运算),可是一般咱们能够从上下文清楚地知道是在讨论前缀运算符仍是中缀运算符。

请勿在不理解*** 运算符的前提下记住它们的全部用法!这些操做符有不少用途,记住每种操做符的具体用法并不重要,重要的是了解你什么时候可以使用这些操做符。我建议使用这篇文章做为一个备忘单或者制做你本身的备忘单来帮助你在 Python 中使用解***


十一、喜欢个人教学风格吗?

想了解更多关于 Python 的知识吗?我每周经过实时聊天分享我最喜欢的 Python 资源、回答 Python 问题。

尚学堂推出《13天搞定Python网络爬虫》视频教程,学习成为Python爬虫工程师,薪资杠杠的!

相关文章
相关标签/搜索