Python logical XOR

How do you get the logical xor of two variables in Python?

For example, I have two variables that I expect to be strings. I want to test that only one of them contains a True value (is not None or the empty string):

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

The ^ operator seems to be bitwise, and not defined on all objects:

>>> 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'
share improve this question
 
3 
How do you define "xor" for a couple of strings? What do you feel "abc" ^ "" should return that it doesn't? –  Mehrdad Afshari  Jan 11 '09 at 12:39
7 
It should return True, rather than raise an exception, since only one of the strings is True as defined by normal Python's bool type. –  Zach Hirsch  Jan 11 '09 at 19:10
10 
I'm amazed that Python doesn't have an infix operator called "xor", which would be the most intuitive, Pythonic implementation. Using "^" is consistent with other languages, but not as blatantly readable as most of Python is. –  mehaase  Nov 11 '12 at 3:50
4 
@MehrdadAfshari The obvious answer to your question is that a xor a is defined as (a and not b) or (not a and b), and so a xor b, when a and b are character strings, or any other types, should yield whatever (a and not b) or (not a and b) yields. –  Kaz  May 14 '13 at 15:57
 
The issue is that documentation is poor. ^ is "bitwise exclusive or", which literally interpreted means bit by bit, not bool by bool. so x'FFFF00' ^ x'FFFF00' should be x'000000'. Or is this only meant to occur on a char by char basis ? cast as numbers ? We need to iterate the shorter string characters to match the length of the longer string. All this should be built in. –  mckenzm  Mar 22 '15 at 2:29

14 Answers

up vote 642 down vote accepted

If you're already normalizing the inputs to booleans, then != is xor.

bool(a) != bool(b)
share improve this answer
 
40 
Although this is clever and short, I'm not convinced it's clean. When someone reads this construct in the code, is it immediately obvious to them that this is an xor operation? I felt obliged to add a comment - a sign for me that I'm writing unclear code and try to apologise with a comment. –  Erik  Mar 19 '12 at 17:25
15 
Perhaps "is it clear that it's an XOR?" is the wrong question. We were just trying to see whether the answer to two questions are the same, and thinking we'd use XOR to implement that. For example, if we want to ensure that we are not comparing apples to oranges, is "if xor( isApple(x), isApple(y) )" really clearer than "if isApple(x) != isApple(y)" ? Not to me! –  AmigoNico  May 21 '12 at 18:27
 
s/answer/answers/ –  AmigoNico  May 21 '12 at 18:36
55 
There is problem with using "!=" as xor. You would probably expect bool(a) != bool(b) != bool(c) to be the same as bool(a) ^ bool(b) ^ bool(c). So do casts to bool, but I would recommend ^. To know what's going up in the first example look up "operator chaining". –  elmo  Jul 25 '12 at 11:50
7 
@elmo: +1 for pointing out the difference, and +1 for teaching me what operator chaining is! I am in the camp that says that != is not as readable as ^. –  mehaase  Nov 11 '12 at 3:58

You can always use the definition of xor to compute it from other logical operations:

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

But this is a little too verbose for me, and isn't particularly clear at first glance. Another way to do it is:

bool(a) ^ bool(b)

The xor operator on two booleans is logical xor (unlike on ints, where it's bitwise). Which makes sense, since bool is just a subclass of int, but is implemented to only have the values 0 and 1. And logical xor is equivalent to bitwise xor when the domain is restricted to 0 and 1.

So the logical_xor function would be implemented like:

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

Credit to Nick Coghlan on the Python-3000 mailing list.

share improve this answer
 
3 
i'd give another vote for referencing your sources if i could :) –  cbrulak  Jan 11 '09 at 21:01
4 
great post, but of all ways to name your parameters, why 'str1' and 'str2'? –  SingleNegationElimination  Jul 4 '09 at 16:55
1 
@Token why not. Do you mean because they aren't very Pythonic? –  orokusaki  Jan 25 '10 at 6:42
1 
@Zach Hirsch Could you use (not a and b) instead of (b and not a) for readability or would the definition be inconsistent with xor. –  orokusaki  Feb 1 '10 at 15:18
5 
You should put the nots first like this (not b and a) or (not a and b) so that it returns the string if there was one, which seems like the pythonic way for the function to operate. –  rjmunro  May 7 '11 at 21:06

Exclusive-or is already built-in to Python, in the operator module:

from operator import xor
xor(bool(a), bool(b))
share improve this answer
 
2 
This is what I needed. When reverse engineering malware lots of times strings are mangled until an XOR operation. Using this chr(xor(ord("n"), 0x1A)) = 't' –  ril3y  Jul 11 '12 at 14:15 
20 
Be careful, this is also bitwise: xor(1, 2) returns 3. From the docstring:  xor(a, b) -- Same as a ^ b. Remember that anything imported from operator is just a functional form of an existing builtin infix operator. –  askewchan  Sep 15 '13 at 16:59 
2 
This xor is bitwise and using it has the same effect of the ^ operator. –  Diego Queiroz  Jan 22 '14 at 13:23
 
@askewchan: The bool type overloads __xor__ to return booleans. It'll work just fine, but its overkill when bool(a) ^ bool(b) does exactly the same thing. –  Martijn Pieters  Nov 3 '14 at 12:48

As Zach explained, you can use:

xor = bool(a) ^ bool(b)

Personally, I favor a slightly different dialect:

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

This dialect is inspired from a logical diagramming language I learned in school where "OR" was denoted by a box containing ≥1 (greater than or equal to 1) and "XOR" was denoted by a box containing =1.

This has the advantage of correctly implementing exclusive or on multiple operands.

  • "1 = a ^ b ^ c..." means the number of true operands is odd. This operator is "parity".
  • "1 = a + b + c..." means exactly one operand is true. This is "exclusive or", meaning "one to the exclusion of the others".
share improve this answer
 
9 
So, True + True + False + True == 3, and 3 != 1, but True XOR True XOR False XOR True == True. Can you elaborate on "correctly implementing XOR on multiple operands"? –  tzot  Jan 11 '09 at 22:59
 
@ΤΖΩΤΖΙΟΥ: Wrap it in a bool() –  BlueRaja - Danny Pflughoeft  Apr 20 '10 at 13:26
2 
@tzot Your example fails, because according to ddaa's solution, you apply the addition on only two variables at a time. So the right way to write it all out would have to be (((((True + True)==1)+False)==1)+True)==1. The answer given here totally generalizes to multiple operands. –  Mr. F  Oct 23 '12 at 21:18
4 
Also, there's a difference between a three-way XOR vs. order-of-operations-grouped set of two XORs. So 3-WAY-XOR(A,B,C) is not the same thing as XOR(XOR(A,B),C). And ddaa's example is the former, while yours assumes the latter. –  Mr. F  Oct 23 '12 at 21:20
1 
@EMS: thanks. Your objections clarified (to me) what ddaa meant. –  tzot  Oct 24 '12 at 21:57
  • Python logical orA or B: returns A if bool(A) is True, otherwise returns B
  • Python logical andA and B: returns A if bool(A) is False, otherwise returns B

To keep most of that way of thinking, my logical xor definintion would be:

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

That way it can return ab, or False:

>>> logical_xor('this', 'that')
False
>>> logical_xor('', '')
False
>>> logical_xor('this', '')
'this'
>>> logical_xor('', 'that')
'that'
share improve this answer
 
3 
This seems bad, or at least weird, to me. None of the other built-in logical operators return one of three possible values. –  Zach Hirsch  Jan 11 '09 at 19:12
1 
@Zach Hirsch: That's why I said "to keep most of that way of thinking" - since there's no good result when both are true or false –  nosklo  Nov 25 '09 at 22:52
 
Logical operation must return logical value, so second "return a or b" looks strange, so second return must return True. –  Denis Barmenkov  Feb 10 '11 at 13:36 
5 
@Denis Barmenkov: Well, note that python logical operators and and or won't return logical value. 'foo' and 'bar' returns 'bar' ... –  nosklo  Feb 13 '11 at 2:11
4 
At first sight, the 2 previous answers seem like the best, but on second thought, this one is actually the only truly correct one, i.e. it's the only one that provides an example of a xor implementation that is consistent with the built-in and and or. However, of course, in practical situations, bool(a) ^ bool(b) or even a ^ b (if a and b are known to be bool) are more concise of course. –  Erik Allik  Jan 13 '12 at 13:48

How about this?

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

will give a if b is false
will give b if a is false
will give False otherwise

Or with the Python 2.5+ ternary expression:

(False if a else b) if b else a
share improve this answer
 

Exclusive Or is defined as follows

def xor( a, b ):
    return (a or b) and not (a and b)
share improve this answer
 
2 
that would return True for xor('this', '') and to follow python's way, it should return 'this'. –  nosklo  Jan 11 '09 at 13:45
 
@nosklo: Take it up with the BDFL, please, not me. Since Python returns True, then that must be Python's way. –  S.Lott  Jan 11 '09 at 14:15
2 
I mean for consistency with the other python logical operators - Python doesn't return True when I do ('this' or ''), it returns 'this'. But in your function xor('this', '') returns True. It should return 'this' as the "or" python builtin does. –  nosklo  Jan 11 '09 at 15:09
7 
Python and and or do short-circuit. Any xor implementation can't short-circuit, so there is already a discrepancy; therefore, there is no reason that xor should operate like and+or do. –  tzot  Jan 11 '09 at 23:12

As I don't see the simple variant of xor using variable arguments and only operation on Truth values True or False, I'll just throw it here for anyone to use. It's as noted by others, pretty (not to say very) straightforward.

def xor(*vars):
    sum = bool(False)
    for v in vars:
        sum = sum ^ bool(v)
    return sum

And usage is straightforward as well:

if xor(False, False, True, False):
    print "Hello World!"

As this is the generalized n-ary logical XOR, it's truth value will be True whenever the number of True operands is odd (and not only when exactly one is True, this is just one case in which n-ary XOR is True).

Thus if you are in search of a n-ary predicate that is only True when exactly one of it's operands is, you might want to use:

def isOne(*vars):
    sum = bool(False)
    for v in vars:
        if sum and v:
            return False
        else:
            sum = sum or v
    return sum
share improve this answer
 
 
For improving this answer: (bool(False) is False) == True. You can just use False on those lines. –  lighttigersoul  Apr 25 '15 at 6:01 

Some of the implementations suggested here will cause repeated evaluation of the operands in some cases, which may lead to unintended side effects and therefore must be avoided.

That said, a xor implementation that returns either True or False is fairly simple; one that returns one of the operands, if possible, is much trickier, because no consensus exists as to which operand should be the chosen one, especially when there are more than two operands. For instance, should xor(None, -1, [], True) return None[] or False? I bet each answer appears to some people as the most intuitive one.

For either the True- or the False-result, there are as many as five possible choices: return first operand (if it matches end result in value, else boolean), return first match (if at least one exists, else boolean), return last operand (if ... else ...), return last match (if ... else ...), or always return boolean. Altogether, that's 5 ** 2 = 25 flavors of xor.

def xor(*operands, falsechoice = -2, truechoice = -2):
  """A single-evaluation, multi-operand, full-choice xor implementation
  falsechoice, truechoice: 0 = always bool, +/-1 = first/last operand, +/-2 = first/last match"""
  if not operands:
    raise TypeError('at least one operand expected')
  choices = [falsechoice, truechoice]
  matches = {}
  result = False
  first = True
  value = choice = None
  # avoid using index or slice since operands may be an infinite iterator
  for operand in operands:
    # evaluate each operand once only so as to avoid unintended side effects
    value = bool(operand)
    # the actual xor operation
    result ^= value
    # choice for the current operand, which may or may not match end result
    choice = choices[value]
    # if choice is last match;
    # or last operand and the current operand, in case it is last, matches result;
    # or first operand and the current operand is indeed first;
    # or first match and there hasn't been a match so far
    if choice < -1 or (choice == -1 and value == result) or (choice == 1 and first) or (choice > 1 and value not in matches):
      # store the current operand
      matches[value] = operand
    # next operand will no longer be first
    first = False
  # if choice for result is last operand, but they mismatch
  if (choices[result] == -1) and (result != value):
    return result
  else:
    # return the stored matching operand, if existing, else result as bool
    return matches.get(result, result)

testcases = [
  (-1, None, True, {None: None}, [], 'a'),
  (None, -1, {None: None}, 'a', []),
  (None, -1, True, {None: None}, 'a', []),
  (-1, None, {None: None}, [], 'a')]
choices = {-2: 'last match', -1: 'last operand', 0: 'always bool', 1: 'first operand', 2: 'first match'}
for c in testcases:
  print(c)
  for f in sorted(choices.keys()):
    for t in sorted(choices.keys()):
      x = xor(*c, falsechoice = f, truechoice = t)
      print('f: %d (%s)\tt: %d (%s)\tx: %s' % (f, choices[f], t, choices[t], x))
  print()
share improve this answer
 

It's easy when you know what XOR does:

def logical_xor(a, b):
    return (a and not b) or (not a and b)

test_data = [
  [False, False],
  [False, True],
  [True, False],
  [True, True],
]

for a, b in test_data:
    print '%r xor %s = %r' % (a, b, logical_xor(a, b))
share improve this answer
 

Sometimes I find myself working with 1 and 0 instead of boolean True and False values. In this case xor can be defined as 

z = (x + y) % 2

which has the following truth table:

     x
   |0|1|
  -+-+-+
  0|0|1|
y -+-+-+
  1|1|0|
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值