Python中a &= b是什么意思?

42

&= 运算符在 Python 中是按位与赋值的缩写,它将变量和另一个数进行按位与操作,并将结果赋值给变量。

以下是一个示例:

```python x = 5 x &= 3 # 将 x 与 3 进行按位与操作,并将结果赋值给 x print(x) # 输出结果为 1 ```

我正在尝试理解 __iand__ 运算符。

我只是不知道 &= 是什么意思,查了一下网上也没找到。

7个回答

47

a &= b是什么意思?

这取决于ab的类型实现,但语义基本上是

a &= b

这是将a与其"AND"操作更新为b。该“AND”操作可以是集合交集、二进制AND操作或其他可由程序员实现的操作。
因此,有多种实现方法。
在下面的部分中,我演示了__and____iand__,并提供了各种类型的多个示例,其中下面具有不同语义(set,其中a对象被就地突变,并且frozensetint是不可变的,因此变量a现在指向新对象)。

解释

可以理解为难以找到相关参考资料。我也很难找到相关参考资料,但它们确实存在。 iiand中表示就地操作,因此它是&的就地运算符。&=如果已经实现,则调用__iand__运算符。如果未实现,则与x = x & y相同。

内置示例、集合:

主要用于更新内置集合类型的交集:
>>> a = set('abc') 
>>> a &= set('cbe')
>>> a
set(['c', 'b'])

这与以下内容相同:
>>> a = set('abc')
>>> a.__iand__(set('cbe'))
set(['c', 'b'])

这与调用set.intersection_update方法非常相似,可以在控制流中使用,就像对任何对象或变量进行原地更新一样(如果对象是不可变的)。

未实现的内置示例

较少使用的不可变的frozenset对象将在原位更新时在内存中被替换,并且变量名将指向内存中的新对象。

>>> a = frozenset('abc')
>>> a &= set('bce')
>>> a
frozenset({'c', 'b'})

在这种情况下,由于frozenset没有实现__iand__方法,因此
>>> a = frozenset('abc')
>>> a.__iand__(set('cbe'))

Traceback (most recent call last):
  File "<pyshell#160>", line 1, in <module>
    a = frozenset('abc'); a.__iand__(set('cbe'))
AttributeError: 'frozenset' object has no attribute '__iand__'

IT技术几乎与其相同。

a = a & set('bce')

*I say nearly because如果你检查字节码,你会发现底层实现对待集合和不可变集合是相同的,即使不可变集合没有__iand__,而集合有,因为每个都调用INPLACE_AND,至少针对已编译的函数。

内置示例,二进制标志:

类似于集合,我们可以使用&=更新二进制选项标志的交集,其中True的值为1。下面,我们演示二进制数字1110和1011的“二进制AND”(类似于交集)是1010:

>>> option_flags = int('1110', 2)
>>> option_flags
14
>>> option_flags &= int('1011', 2)
>>> option_flags
10
>>> bin(option_flags)
'0b1010'

由于int对象与frozenset示例一样不可变,因此这实际上只是将变量option_flags重新分配给新计算的值。


所以它是两个元素之间的交集。 - Shmack
1
@Shmack - 我写了一个新的开头,专门回答那个评论。 - Russia Must Remove Putin

15

与其他答案相反,a &= b不是a = a & b的简写,尽管我承认对于像整数这样的内置不可变类型,它通常会表现出类似的行为。

a &= b如果可用的话可以调用特殊方法__iand__。为了看到区别,让我们定义一个自定义类:

class NewIand(object):
    def __init__(self, x):
        self.x = x
    def __and__(self, other):
        return self.x & other.x
    def __iand__(self, other):
        return 99  

之后我们有

>>> a = NewIand(1+2+4)
>>> b = NewIand(4+8+16)
>>> a & b
4
>>> a = a & b
>>> a
4

但是

>>> a = NewIand(1+2+4)
>>> b = NewIand(4+8+16)
>>> a &= b
>>> a
99

8

这是一个简写形式:

a = a & b

&按位与(请参阅链接以获取进一步的解释),如果ab都是intlong

否则,该语句等同于:

a = a.__iand__(b)

如果为 a 定义了 __iand__

1
@JBurnham,像 a += b 这样的写法是“a = a + b”的简写。更恰当地说,我会说,“就像 C# 中的 &=”。 - Stefano Sanfilippo

4
简单来说,它进行位运算。例如5的二进制为0101,3的二进制为0011,在它们之间进行“And”运算(当两个位都是1时结果为1,否则为0),你将得到二进制的0001,即十进制的1。
x = 5
x &= 3
print(x)
output >>> 1

2

这意味着按位与操作。

例如:

x = 5
x &= 3 #which is similar to x = x & 3
print(x)

答案:1
它是如何工作的?
The binary of 5 is : 0 1 0 1
The binary of 3 is : 0 0 1 1

AND operation : (If both sides are 1/True then result is 1/True)

0 1 0 1  #Binary of 5
0 0 1 1  #Binary of 3
---------------------
0 0 0 1  #Binary of 1

所以,答案是1。

2

这与+=非常相似,意思是

a = a & b

0
免责声明:我专注于__and__和__iand__,但下面的描述对于任何其他运算符组合及其原地替代都适用。
简而言之:
& / __and__ &= / __iand__
副作用(例如,对其中一个参数的改变) 虚拟可能,但不鼓励 高度预期
结果 新实例 相同实例
更详细的回答:
重要的是要理解__and__函数应该是纯粹的,这意味着它不应该有副作用,并且对于相同的参数必须返回相同的结果。
a = 5
b = 3

print(a & b)
print(a.__and__(b))
print(int.__and__(a, b))

无论你调用这个函数多少次,结果总是1。由于这个函数没有副作用,所以从中返回一个值是很自然的,因为结果必须以某种方式存储。此外,a & b是一个表达式,所以期望返回一个结果。
另一方面,我们有__iand__。开头的i代表“原地”。大多数情况下,它是由使用&=运算符而触发的,实际上这是一个所谓的“增强赋值”运算符,关键字在这里是“赋值”。在Python中,赋值是语句,这意味着它们不会产生结果,与表达式相反,但可能会有副作用。&=的预期副作用是改变左侧操作数的内容。
看一下下面的代码:
a = 5
b = 3

print(a &= b)

如果你尝试这样做,是行不通的,因为print需要一个表达式作为参数。但是,你可以这样做:
print(a.__iand__(b))

然而,这也会失败,因为int没有实现__iand__(可以理解,原始类型是不可变的)。那么,为什么下面的代码能够正常运行并输出1呢?
a &= b
print(a)

当Python找不到`__iand__`时,它会将`a &= b`展开为`a = a & b`,实际上调用了`a.__and__(b)`,并覆盖了`a`变量的内容。可变整数类型可以如下所示:
class MutableInt:
    def __init__(self, value):
        self.value = value

    def __and__(self, other):
        return MutableInt(self.value & other.value)

    def __iand__(self, other):
        self.value &= other.value
        return self

    def __str__(self):
        return str(self.value)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接