Python星号特性
在本文中,当我讨论
*
和**
时,我指的是*和**前缀操作符
,而不是中缀操作符。
>>> 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. 使用**将字典解压到其他字典中
星号用于将可迭代对象拆分并分别作为函数参数
>>> fruits = ['lemom', 'pear', 'watermelon', 'tomato']
>>> print(fruits[0], fruits[1], fruits[2], fruits[3])
lemon pear watermelon romato
>>> print(*fruits)
lemon pear watermelon tomato
*运算符在这里远不止是语法糖而已。要想用一个特定的迭代器将所有项作为独立的参数传输,若不是用*是不可能做到的,除非列表的长度是固定的。
def tranpose_list(list_of_lists):
return [list(row) for row in zip(*list_of_lists)]
print(tranpose_list([[1, 4, 7], [2, 5, 8], [3, 6, 9]]))
**
操作符完成了类似的操作,只不过使用了关键字参数。**
运算符允许我们获取键-值对字典,并在函数调用中将其解压为关键字参数。
>>> date_info = {'year':'2020', 'month':'01', 'day':'01'}
>>> filename = "{year}-{month}-{day}.txt".format(**date_info)
2020-01-01.txt
多次使用**也可以达到相似的效果:
>>> 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)
>>> print(filename)
2020-01-01-Beethoven-Symphony No 5.txt
星号用于压缩被传递到函数中的参数
# 在定义函数时,
# *运算符可用于捕获传递给函数的位置参数。位置参数的数量不受限制。
# 捕获后被存储在一个元组中,而元组是可迭代对象,在函数中会一个一个遍历拿出。
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的print和zip函数接受的位置参数数量不受限制。
*
的这种参数压缩用法,允许我们创建像print和zip一样的函数,接受任意数量的参数。
**
运算符也有另外一个功能:
我们在定义函数时,可以使用**捕获传进函数的任何关键字参数到一个字典中:
def tag(tag_name, **attributes):
attribute_list = [
f'{name}="{value}"' for name, value in attributes.items()
]
return f"<{tag_name} {' '.join(attribute_list)}>"
>>> print(tag('a', href="http://treyhunner.com"))
>>> print(tag('img', height=20, width=40, src="face.jpg"))
只有关键字参数的位置参数
在Python3中,我们现在拥有了一种特殊的语法来接受只有关键字的函数参数。
只有关键字的参数是只能使用关键字调语法 来指定的函数参数,也就意味着不能按照位置来指定他们。
在定义函数时,为了接受只有关键字的参数,我们可以将命名参数放在
*
后。
def get_multiple(*keys, dictionary, default=None):
return [
dictionary.get(key, default) for key in keys
]
>>> fruits = {'lemon': 'yellow', 'orange': 'orange', 'tomato': 'red'}
>>> print(get_multiple('lemon', 'tomato', 'squash', dictionary=fruits, default='unknown'))
['yellow', 'red', 'unknown']
※如果按照位置来指定他们,我们会得到一个报错。
print(get_multiple('lemon', 'tomato', 'squash', fruits, 'unknown'))
Traceback (most recent call last):
File "E:/python-s/python特性测试/星号特性.py", line 54, in <module>
print(get_multiple('lemon', 'tomato', 'squash', fruits, 'unknown'))
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。
>>> print(list(with_previous([2, 1, 3], fillvalue=0)))
[(0, 2), (2, 1), (1, 3)]
但像这样就不可以:
>>> print(list(with_previous([2, 1, 3], 0)))
Traceback (most recent call last):
File "E:/python-s/python特性测试/星号特性.py", line 71, in <module>
print(list(with_previous([2, 1, 3], 0)))
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 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.
在sorted的官方说明中,有一个单独的*参数。
星号用于元组拆包
python3还新添了一种
*
运算符的使用方式, 它只与上面定义函数时和调用函数时*
的使用方式相关。现在,
*
操作符也可以用于元组拆包:
>>> 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']
列表文字中的星号
python3.5通过PEP 448 引入了大量与
*
相关的新特性。其中最大的新特性之一是能够使用*
将迭代器转储到新列表中。假设你有一个函数,它以任意序列作为输入,返回一个列表,其中该序列和序列的倒序连接在一起:
def palindeomify(sequence): return list(sequence) + list(reversed(sequence))
此函数需要多次将序列转换为列表, 以便连接列表并返回结果。在python3.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}
>>> uppercase_fruits = (f.upper() for f in fruits)
{'LEMON',
'PEAR',
'TOMATO',
'WATERMELON',
'lemon',
'pear',
'tomato',
'watermelon'}
注意,上面的最后一行使用了一个列表和一个生成器,并将它们转储到一个新的集合中。在此之前,并没有一种简单的方法可以在一行代码中完成这项工作。曾经有一种方法可以做到这一点,可以并不容易被记住或发现。
两个星号用于字典文本
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'}
不过,
**
操作符不仅仅可以用于合并两个字典。例如,我们可以在复制一个字典的同时添加一个新值:
>>> date_info = {'year':"2020", 'month':"01", 'day':"01"}
>>> event_info = {**date_info, 'group':"Python Meetup"}
>>> event_info
{'year': '2020', 'month': '01', 'day': '01', 'group': 'Python Meetup'}
或者在复制/合并字典的同时重写特定的值:
>>> event_info = {'year': '2020', 'month': '01', 'day': '01', 'group': 'Python Meetup'}
>>> new_info = {**event_info, 'day':"14"}
>>> new_info
{'year': '2020', 'month': '01', 'day': '14', 'group': 'Python Meetup'}
请勿在不理解
*
和**
运算符的前提下记住它们的所有用法!这些操作符有很多用途,记住每种操作符的具体用法并不重要,重要的是了解你何时能够使用这些操作符。我建议使用这篇文章作为一个备忘录或者制作你自己的备忘单来帮助你在Python中使用解
*
和**
。