missing 'xor' Boolean operator

Steven D'Aprano steve at REMOVE-THIS-cybersource.com.au
Fri Jul 17 17:30:11 CEST 2009

On Fri, 17 Jul 2009 16:34:57 +0200, Jean-Michel Pichavant wrote:

> Steven D'Aprano wrote:
>> On Thu, 16 Jul 2009 15:53:45 +0200, Jean-Michel Pichavant wrote:
>> Given three result codes, where 0 means "no error" and an arbitrary
>> non- zero integer means some error, it is simple and easy to write:
>> failed = result_1 or result_2 or result_3
>> The equivalent:
>> failed = (result_1 != 0) or (result_2 != 0) or (result_3 != 0) # or if
>> you prefer:
>> succeeded = (result_1 == 0) and (result_2 == 0) and (result_3 == 0)
> [snip]
> This is, I guess, where we disagree. I find the second proposal less
> error prone, and universally understandable unlike the first one. It may
> be verbose, it may look even lame to some people, but in the end this is
> perfectly reliable, because you manipulate only False or True within the
> boolean operations.

Because it is verbose, it is more error-prone. The more code you have to 
write, the more opportunities you have to make mistakes.

(This holds up to a point, beyond which being more and more terse also 
leads to more errors.)

Boolean algebra is notorious for programmer errors. The more complicated 
the boolean expression, the more often programmers get it wrong. The more 
book-keeping they have to do, the easier it is to get it wrong. All those 
(result != 0) comparisons are mere book-keeping, they don't add anything 
to the code except to force the flag to be True or False.

> The first form does not clearly show what is the failed criteria.

Of course it does: at least one of the three result codes is non-zero. As 
a bonus, failed will be assigned the first non-zero result code (if any).

Your preferred form, on the other hand, folds all the possible error 
codes into True, throwing away useful information:

>>> result_1 = 0  # success
>>> result_2 = 0
>>> result_3 = 15  # failure
>>> failure = result_1 or result_2 or result_3
>>> failure
>>> failure = (result_1 != 0) or (result_2 != 0) or (result_3 != 0)
>>> failure

> It
> just happens by coincidence that in this case the failed criteria
> matches the Nothingness of result_1, result_2, result_3. What if results
> may be 'OK' or 'KO'.

Obviously the test you write depends on the data you have to work with. 
Do you think that's surprising?

> failed = result_1 or result_2 or result_3 won't work.

Not in the example you gave, no. Although if I had to work with lots and 
lots of strings of the form 'OK' and 'KO', I'd consider sub-classing 

>>> class OKString(str):
...     def __nonzero__(self):
...         if self == 'OK': return True
...         elif self == 'KO': return False
...         else:
...             raise ValueError('invalid result code')
>>> a = OKString('OK')
>>> b = OKString('KO')
>>> c = OKString('KO')
>>> if a and b and c:
...     print "Success!"
... else:  print "Failed!"

(I wouldn't bother if I only needed one or two such tests, but if I had 
lots of them, I'd consider it.)

> failed = (result_1 =='KO') or (result_2 =='KO') or (result_3 =='KO') is
> lame but reliable.

Yes, it's reliably complicated, reliably easy to get wrong, reliably 
harder to read, and it reliably throws away information.

But apart from those disadvantages, it does the job you want it to do.


More information about the Python-list mailing list