Python中的星号操作符(* 以及**)

Python中的星号操作符(* 以及**)

在Python中,***运算符有许多用途。这两个运算符有时对于新手程序员以及从其他可能没有完全相等运算符的编程语言转移过来的人来说可能有些神秘。我想讨论一下这些运算符是什么,以及它们的许多用法。

***运算符的功能随着时间的推移逐渐增强,我将讨论您目前可以使用这些运算符的所有方式,并指出哪些用法仅在Python的现代版本中起作用。因此,如果您仍然在用Python 2, 建议尝试一下Python 3,,因为Python 3为这些运算符添加了许多新的用途。

本文不讨论的内容

在本文中,当我讨论***时,我指的是***前缀运算符,而不是中缀运算符。

也就是说我不是在讨论乘法和指数等运算:

>>> 2 * 5  # 浮点数中缀的单星号表示算术乘法运算
10
>>> 2 ** 5 # 浮点数中缀的双星号表示算术指数运算
32
>>> [1]*10  #列表与整数间的中缀单星号表示列表元素复制
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

那么我们在谈论什么呢?

本文要讨论的内容

我们正在讨论***前缀运算符,也就是在变量之前使用的*运算符。例如:

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

该代码中展示了*的两种用法,而没有展示**的用法。

这包括:

  • 使用***将参数传递给函数
  • 使用***捕获传递给函数的参数
  • 使用*接受仅限关键字的参数
  • 使用*在元组解包期间捕获项目
  • 使用*将可迭代对象解包为列表/元组
  • 使用**将字典解包为其他字典

即使您认为您熟悉使用***的所有方式,我建议查看下面的每个代码块,以确保它们都是您熟悉的内容。在过去的几年里,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)行将水果列表中的所有项作为单独的参数传递给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': "2023", 'month': "12", 'day': "02"}
>>> filename = "{year}-{month}-{day}.txt".format(**date_info)
>>> filename
'2023-12-02.txt'

根据我的经验,使用**将关键字参数解包到函数调用中并不是特别常见。我最常见到的情况是在练习继承时:对super()的调用通常包括*。

在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

这个函数接受一个iterable参数,可以通过位置指定(作为第一个参数)或通过其名称和fillvalue参数指定,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 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还添加了一种与*-when-defining-a-function和*-when-calling-a-function功能略有关联的新方式,该方式涉及到元组解包:

>>> 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中元组解包的文章中的例子。在那篇文章中,我展示了*运算符的这种用法有时可以作为序列切片的替代方法。

通常在说明*时,我会指出在单个多重赋值调用中只能使用一个*表达式。这在技术上是不准确的,因为在嵌套解包中可以使用两个*表达式:

>>> fruits = ['lemon', 'pear', 'watermelon', 'tomato']
>>> ((first_letter, *remaining), *other_fruits) = fruits
>>> remaining
['e', 'm', 'o', 'n']
>>> other_fruits
['pear', 'watermelon', 'tomato']

然而,我从未见过对此有很好运用的情况,即使找到了,我也不认为我会推荐使用,因为它似乎有点难以理解。

这个功能在PEP 3132中添加到Python 3.0。

星号用于迭代列表

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'}

请注意,上述的最后一行将一个列表和一个生成器转储到一个新的集合中。在使用*之前,以前没有一行代码可以轻松实现这个操作。以前是有一种方式可以做到,但不容易记住或发现:

>>> set().union(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'}

这不仅仅用于合并两个字典,例如,我们可以在复制字典的同时向其添加新值:

>>> 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世界),也听说过它被简单地称为“星号”。

我倾向于将这些运算符称为“星号”和“双星号”或“星号星号”。这并没有将它们与它们的中缀表亲(乘法和指数)区分开,但上下文通常很明显,我们是在谈论前缀还是中缀运算符。

如果你不理解***,或者担心记住它们的所有用法,不要担心!这些运算符有很多用途,记住每个运算符的具体用法并不像了解何时可以使用这些运算符那样重要。我建议将这篇文章当作速查表,或制作自己的速查表,以帮助你在Python中使用***

```python
class BertPooler(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.dense = nn.Linear(config.hidden_size, config.hidden_size)
        self.activation = nn.Tanh()

    def forward(self, hidden_states):
        # We "pool" the model by simply taking the hidden state corresponding
        # to the first token.
        first_token_tensor = hidden_states[:, 0]
        pooled_output = self.dense(first_token_tensor)
        pooled_output = self.activation(pooled_output)
        return pooled_output
from transformers.models.bert.configuration_bert import *
import torch
config = BertConfig.from_pretrained("bert-base-uncased")
bert_pooler = BertPooler(config=config)
print("input to bert pooler size: {}".format(config.hidden_size))
batch_size = 1
seq_len = 2
hidden_size = 768
x = torch.rand(batch_size, seq_len, hidden_size)
y = bert_pooler(x)
print(y.size())
```

  • 28
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Python,两个星号(**)的用法通常用于将关键字参数(keyword arguments)以字典的形式导入到函数。例如,当函数定义使用**kwargs时,可以传入任意数量的关键字参数,这些参数将被打包成一个字典,并导入到函数。 例如,在下面的函数定义: ```python def foo(**kwargs): print(kwargs) ``` 我们可以通过使用两个星号来传递关键字参数: ```python foo(a=1, b=2, c=3) ``` 输出结果将是一个包含关键字参数的字典: ``` {'a': 1, 'b': 2, 'c': 3} ``` 另外,星号*还有另一种用法,即通过使用*操作符来解包列表或元组的参数。例如,当我们有一个包含参数的列表或元组时,可以使用*操作符将其解包并作为独立的参数传递给函数。 举个例子,假设我们有一个包含两个元素的列表args,并且我们想将这两个元素作为参数传递给range函数,我们可以这样做: ```python args = [3, 6] list(range(*args)) ``` 这将等价于使用分别的参数调用range函数: ```python list(range(3, 6)) ``` 输出结果将是一个包含从3到5的数字的列表: ``` <span class="em">1</span><span class="em">2</span> #### 引用[.reference_title] - *1* *2* [Python函数参数前面一个星号(*)和两个星号(**)的含义](https://blog.csdn.net/Dust_Evc/article/details/121853377)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值