如何在Python中获取两个变量的逻辑异或?

967

在Python中,如何获得两个变量的逻辑异或

例如,我有两个变量,我希望它们都是字符串。我想测试只有其中一个包含True值(不是None或空字符串):

str1 = raw_input("Enter string one:")
str2 = raw_input("Enter string two:")
if logical_xor(str1, str2):
    print "ok"
else:
    print "bad"

^ 运算符是按位运算符,不适用于所有对象:
>>> 1 ^ 1
0
>>> 2 ^ 1
3
>>> "abc" ^ ""
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for ^: 'str' and 'str'

6
你如何定义一对字符串的“异或(xor)”操作?你认为对于“abc” ^ ""这个操作,它应该返回什么而实际上它却不返回这个值? - Mehrdad Afshari
24
由于根据Python的bool类型定义,只有其中一个字符串是True,因此应该返回True而不是引发异常。 - Zach Hirsch
71
我惊讶于Python没有一个称为“异或”的中缀运算符,这将是最直观、最符合Python风格的实现。使用“^”与其他语言保持一致,但不如Python的大部分代码那样易读。 - Mark E. Haase
20
你的问题的明显答案是,a xor a 被定义为 (a and not b) or (not a and b),因此当 ab 为字符串或任何其他类型时,a xor b 应该产生 (a and not b) or (not a and b) 的结果。 - Kaz
2
问题在于文档不够详细。"^"是按位异或,字面意思是逐位进行,而不是逐个布尔值进行。因此,x'FFFF00' ^ x'FFFF00'应该是x'000000'。或者这只是针对char的逐个比较?需要将其转换为数字?我们需要迭代较短字符串的字符以匹配较长字符串的长度。所有这些都应该内置。 - mckenzm
如果这有帮助的话,int("0xFFFF00", 16) ^ int("0xFFFF00", 16) == int(0),所以你必须将其转换为数字并在结果上调用 hex,然后在左侧填充足够多的 0 以达到最长长度。 - Guimoute
29个回答

1655

如果您已经将输入规范化为布尔值,则 != 表示异或。

bool(a) != bool(b)

233
虽然这段代码短小巧妙,但我并不认为它很清晰。当有人阅读代码中的这个结构时,他们是否立刻能够明白这是一个异或操作?我感到有必要添加注释——这对我来说是写了不清晰的代码并试图通过注释来弥补的信号。 - user188041
70
也许“是否清楚它是一个异或”不是正确的问题。我们只是想看看两个问题的答案是否相同,并考虑使用异或来实现这一点。例如,如果我们想确保我们不是在比较苹果和橙子,那么“if xor(isApple(x), isApple(y))”真的比“if isApple(x) != isApple(y)”更清晰吗? 对我来说不是! - AmigoNico
136
使用 "!=" 作为异或运算存在问题。你可能希望 bool(a) != bool(b) != bool(c) 与 bool(a) ^ bool(b) ^ bool(c) 相同,但进行 bool 强制转换时会出现问题。我建议使用 "^" 运算符。如果想了解第一个示例的含义,请查找 "operator chaining"。 - elmo
25
感谢 @elmo 指出了这个区别,同时也感谢他/她教会我什么是操作符链!我倾向于认为“!=”不如“^”易读。 - Mark E. Haase
23
应该改为 bool(a) is not bool(b) 吗? - RNA
显示剩余15条评论

682

你总是可以使用异或的定义从其他逻辑操作中计算它:

(a and not b) or (not a and b)

但对我来说,这有点太冗长了,在第一眼看上去也不是很清晰。另一种做法是:

bool(a) ^ bool(b)

在两个布尔值上应用异或运算符是逻辑异或(与对整数的位运算不同)。这是有道理的,因为bool只是int的子类,但实现时只有01两个取值。当域被限制在01时,逻辑异或等价于位异或。

因此,logical_xor函数将被实现如下:

def logical_xor(str1, str2):
    return bool(str1) ^ bool(str2)

感谢Nick Coghlan在Python-3000邮件列表中的贡献。


15
好的文章,但为什么要用“str1”和“str2”来命名参数? - SingleNegationElimination
2
@Token 为什么不呢?你是指它们不太符合 Python 的风格吗? - orokusaki
2
@Zach Hirsch你能否使用(not a and b)代替(b and not a)以获得可读性,或者这个定义会与异或不一致。 - orokusaki
11
你应该先放置 not,像这样 (not b and a) or (not a and b),这样如果有字符串返回,它似乎是函数运行的 Pythonic 方式。 - rjmunro
3
@TokenMacGuy:你建议他们用什么名字代替? - user541686
显示剩余3条评论

240

位运算的异或已经内置在Python中,可以使用operator模块(与^运算符相同):

from operator import xor
xor(bool(a), bool(b))  # Note: converting to bools is essential

4
这就是我所需要的。在反向工程恶意软件时,很多时候字符串会被加密直到进行异或操作。使用这个 chr(xor(ord("n"), 0x1A)) = 't'。 - ril3y
98
注意,这也是按位异或:xor(1, 2) 返回 3。从文档字符串可以看出:xor(a, b) -- Same as a ^ b.请记住,从 operator 导入的任何内容都只是现有内置中缀运算符的函数形式。 - askewchan
11
bool类型重载了 __xor__ 操作符来返回布尔值。虽然这样做可以正常工作,但当 bool(a) ^ bool(b) 可以达到同样的效果时,这样做就有点过犹不及了。 - Martijn Pieters
7
@Quantum7:是的,但我不确定你为什么要告诉我这个。我只是说bool类型实现了__xor__方法,特别是因为^会调用它。重点是,bool(a) ^ bool(b)可以正常工作,这里没有必要使用operator.xor()函数。 - Martijn Pieters
1
@EdwardSpencer:当你需要将表达式参数化时,operator模块非常棒。但是,在Arel添加^运算符之前,这个答案的措辞似乎暗示了operator.xor()是唯一的方法。 - Martijn Pieters
显示剩余4条评论

55

正如Zach所解释的那样,你可以使用:

xor = bool(a) ^ bool(b)

个人而言,我更喜欢稍微不同的方言:

xor = bool(a) + bool(b) == 1

这种方言的灵感来源于我在学校学习的逻辑图形语言,其中"OR"由一个包含≥1(大于等于1)的框表示,而“XOR”则由一个包含=1的框表示。

这种方式的优点是可以正确地在多个操作数上实现异或运算。

  • "1 = a ^ b ^ c..."意味着真值操作数的数量为奇数。这个运算符是“奇偶校验”。
  • "1 = a + b + c..."意味着只有一个操作数为真。这是“异或”,意思是“排除其他项的一项”。

12
因此,True + True + False + True == 3,而3 != 1,但True XOR True XOR False XOR True == True。您能否详细说明“正确实现多个操作数的异或运算”? 可以将多个操作数视为一个序列,并对该序列中的每个元素执行异或运算。从第一个元素开始,将其与下一个元素进行异或运算,然后将结果与后面的元素继续进行异或运算,直到序列结束。如果存在奇数个True,则结果为True;如果存在偶数个True,则结果为False。 - tzot
4
根据ddaa的解决方案,@tzot的示例存在问题,因为您一次只对两个变量应用加法运算。因此,正确的编写方式应该是(((((True + True)==1)+False)==1)+True)==1。这里给出的答案完全适用于多个操作数。 - ely
9
同样,三路异或和两个异或操作按顺序分组的集合之间存在区别。因此,3-WAY-XOR(A,B,C)并不等同于XOR(XOR(A,B),C)。而ddaa的示例是前者,而你的假设是后者。 - ely
3
@Mr.F 您的解释并不能为这个答案开脱。在Python中,如果您只执行True + True + False + True,您确实会得到3,而True + True + False + True == 3返回True,而True + True + False + True == 1返回False。换句话说,这里的答案无法正确地推广;要使其正确推广,您需要做额外的工作。同时,一个简单的True ^ True ^ False ^ True可以按预期工作。 - jpmc26
5
@jpmc26 我不理解你的评论。加法方法旨在概括操作,其中您想要检查恰好一个操作数为 True,即多元异或。这与例如 A XOR B XOR ... XOR Z 是不同的操作。换句话说,如果您计划使用基于加法的版本,则在提交操作数 True + True + False + True 后,您应该期望结果为 False,因为其中超过一个是 True,这适用于条件检查 == 1 - ely
显示剩余10条评论

34
  • Python逻辑运算符orA or B:如果bool(A)True,则返回A,否则返回B
  • Python逻辑运算符andA and B:如果bool(A)False,则返回A,否则返回B

为了保持这种思考方式,我的逻辑异或定义将是:

def logical_xor(a, b):
    if bool(a) == bool(b):
        return False
    else:
        return a or b

这样它就可以返回abFalse

>>> logical_xor('this', 'that')
False
>>> logical_xor('', '')
False
>>> logical_xor('this', '')
'this'
>>> logical_xor('', 'that')
'that'

5
这对我来说看起来不好,或者至少很奇怪。其他内置逻辑运算符都不会返回三个可能值中的一个。 - Zach Hirsch
2
@Zach Hirsch:这就是为什么我说“保持大多数思考方式”的原因-因为当两者都是真或假时,没有好的结果。 - nosklo
逻辑运算必须返回逻辑值,因此第二个“return a or b”的样子很奇怪,所以第二个return必须返回True。 - Denis Barmenkov
10
请注意,Python 的逻辑运算符 andor 并不会返回逻辑值。例如 'foo' and 'bar' 返回的是 'bar' ... (原文未完) - nosklo
8
乍一看,前两个答案似乎是最好的,但经过深思熟虑,只有这个才是真正正确的,也就是说,它是唯一一个提供了一个与内置的andor一致的xor实现示例的。然而,在实际情况下,当然,bool(a) ^ bool(b)或甚至是a ^ b(如果已知abbool)更为简明。 - Erik Kaplun

33

我尝试了几种方法(包括使用 ShadowRanger 建议的 truth() 函数)。

%timeit  (not a) ^  (not b)   # 47 ns
%timeit  (not a) != (not b)   # 44.7 ns
%timeit truth(a) != truth(b)  # 116 ns
%timeit  bool(a) != bool(b)   # 190 ns

16
那是我白白浪费了100纳秒的时间;-) - Arel
6
在一个中间时间点,你可以在模块的顶部执行 from operator import truth,并测试 truth(a) != truth(b)bool 作为一个构造函数,在 C 级别上有很多不可避免的开销(它必须将参数接受为等价的 *args, **kwargs 并解析 tupledict 来提取它们),而 truth(作为函数)可以使用一个优化的路径,不需要 tupledict,并且运行时间约为基于 bool 的解决方案的一半(但仍然比基于 not 的解决方案长)。 - ShadowRanger
这是指哪个Python实现的哪个版本? - Lutz Prechelt
@LutzPrechelt,不幸的是我不记得了;可能是3.5。 - Rugnar
请注意,您的计时是针对Python 3的。在Python 2.7中,notbool的计时结果相同,但truth(a) != truth(b)的计时约为50纳秒,而不是100+纳秒。因此与not方法相当。 - Ed_
(not a) is not (not b)(not a) != (not b)更快。 - benrg

28

Python有一个按位异或运算符,它是^

>>> True ^ False
True
>>> True ^ True
False
>>> False ^ True
True
>>> False ^ False
False
您可以在应用异或 (^) 之前将输入转换为布尔值来使用它:
bool(a) ^ bool(b)

(编辑 - 感谢Arel)


你的回答应明确说明 ^ 是一个按位异或运算符(不是像问题所问的逻辑异或)。bool(2) ^ bool(3) 的结果与 bool(2 ^ 3) 不同。 - Arel
2
@Arel 但事实并非如此。a ^ b 是多态的。如果 abbool 实例,那么结果也将是 bool。这种行为很难称之为“按位异或”。 - Alfe
@Alfe,重要的一点是首先必须将_values转换为布尔值。Python文档将^定义为位运算符,即使对于boolint类型,类型也会被保留,这是一个有趣的观点。注意:True ^ 23,这证明它确实是按位运算。 - Arel
1
@Arel 是的,bool ^ int 的情况有点像先将一切都强制转换为 int。然而,Python 内置了 ^ 运算符,适用于 int 中的多个比特位和在 bool 中表示的一个比特位。因此,两者都是按位运算,但对于单个比特位的按位异或,就是布尔值的逻辑异或。 - Alfe
我总是不喜欢使用这个操作符,尽管我知道它是 xor,出于工程背景的原因,对我来说,这直观地感觉像数学中的幂运算,即 2^3 = pow(2,3),这意味着我总是明确地进行注释以防止混淆。 - Nicholas Hamilton

17

简单易懂:

sum(bool(a), bool(b)) == 1

如果您想要的是一种独占性选择,即从n个选项中选择1个选项,则可以将其扩展为多个参数:

sum(bool(x) for x in y) == 1

4
sum(map(bool, y)) % 2 == 1 - warvariuc
1
如果你只有两个变量,我认为使用sum的理由不大,而bool(a) + bool(b) == 1则可以实现同样的功能。 - user3064538
@Boris Potayto potarto - c z
@cz 很高兴你同意其中一个明显是错误的 :) - user3064538

13

在Python中获取两个或多个变量的逻辑异或:

  1. 将输入转换为布尔值
  2. 使用位异或运算符(^operator.xor)

例如:

bool(a) ^ bool(b)
当将输入转换为布尔值时,按位异或变成逻辑异或。
请注意,接受的答案是错误的: != 在Python中不同于异或,因为存在运算符链接的微妙差别。
例如,使用!=时下面三个值的异或结果是错误的:
True ^  False ^  False  # True, as expected of XOR
True != False != False  # False! Equivalent to `(True != False) and (False != False)`

(顺便说一下,我试图编辑已接受的答案以包含此警告,但我的更改被拒绝了。)


13

假设A和B是布尔类型。

A is not B

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