tags:

views:

11325

answers:

8

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'
+44  A: 

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.

Zach Hirsch
i'd give another vote for referencing your sources if i could :)
cbrulak
great post, but of all ways to name your parameters, why 'str1' and 'str2'?
TokenMacGuy
@Token why not. Do you mean because they aren't very Pythonic?
orokusaki
@orokusaki: because they don't seem to suggest their intended use very well.
TokenMacGuy
@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
I'm sitting in work thinking; this is a great answer. Well said.
kobrien
+6  A: 
  • Python logical or: A or B: returns A if bool(A) is True, otherwise returns B
  • Python logical and: A 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 a, b, or False:

>>> logical_xor('this', 'that')
False
>>> logical_xor('', '')
False
>>> logical_xor('this', '')
'this'
>>> logical_xor('', 'that')
'that'
nosklo
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
I thought they did, actually. Try 'hello' or 'goodbye'
Tartley
@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
A: 

Exclusive Or is defined as follows

def xor( a, b ):
    return (a or b) and not (a and b)
S.Lott
that would return True for xor('this', '') and to follow python's way, it should return 'this'.
nosklo
@nosklo: Take it up with the BDFL, please, not me. Since Python returns True, then that *must* be Python's way.
S.Lott
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
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.
ΤΖΩΤΖΙΟΥ
+3  A: 

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".
ddaa
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"?
ΤΖΩΤΖΙΟΥ
@ΤΖΩΤΖΙΟΥ: Wrap it in a `bool()`
BlueRaja - Danny Pflughoeft
+1  A: 

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
MizardX
+33  A: 

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

bool(a) != bool(b)
Coady
brilliant, wow, never occurred to me!
Seun Osewa
Same here. Useful and elegant.
e-satis
A: 

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()
A: 

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
micdah