16

I'm going through the LPTHW and I came across something I cannot understand. When will it ever be the case that you want your boolean and or or to return something other than the boolean? The LPTHW text states that all languages like python have this behavior. Would he mean interpreted vs. compiled languages or duck typed vs static typed languages?

I ran the following code:

>>> False and 1
False
>>> True and 1
1
>>> 1 and False
False
>>> 1 and True
True
>>> True and 121
121
>>> False or 1
1
>>> False or 112
112
>>> False or "Khadijah"
'Khadijah'
>>> True and 'Khadijah'
'Khadijah'
>>> False or 'b'
'b'
>>> b = (1, 2, "K")
>>> b
(1, 2, 'K')
>>> False or b
(1, 2, 'K')
>>> 

Please help me understand whats going on here.

According to the documentation: http://docs.python.org/2/library/stdtypes.html

Operations and built-in functions that have a Boolean result always return 0 or False for false and 1 or True for true, unless otherwise stated. (Important exception: the Boolean operations or and and always return one of their operands.)

According to LPTHW: http://learnpythonthehardway.org/book/ex28.html Why does "test" and "test" return "test" or 1 and 1 return 1 instead of True? Python and many languages like it return one of the operands to their boolean expressions rather than just True or False. This means that if you did False and 1 you get the first operand (False) but if you do True and 1 your get the second (1). Play with this a bit.

Dan Oberlam
  • 2,435
  • 9
  • 36
  • 54
  • 2
    Boolean operations are also lazy. ```and``` operations will stop evaluating operands when the first False operand is found. ```or``` operations will stop evaluating operands when the first True operand is found. – wwii Mar 23 '14 at 23:45
  • If all the operands evaluate to True, an ```and``` operation will return the last operand. ```or``` operations will return the first operand that evaluates to True. There are use cases - remember this and one day you will say aha! – wwii Mar 23 '14 at 23:49

4 Answers4

16

I think you're somehow confused about what the docs says. Take a look at these two docs sections: Truth Value Testing and Boolean Operators. To quote the last paragraph on the fist section:

Operations and built-in functions that have a Boolean result always return 0 or False for false and 1 or True for true, unless otherwise stated. (Important exception: the Boolean operations or and and always return one of their operands.)

As you can see, you're right about operations and built-in functions but see the Important exception part, it is well stated that the Boolean operators will return one of their operands.

Now, what they can return depends hardly on the operator's short circuit logic. For or operator, it will return the first truthy value in the expression, since when it finds one, the whole expression is true. In case of every operand being falsey, or will return the last operand, meaning that it iterated over every one of them not being able to find a truthy one.

For and operator, if the expression is true, it will return the last operand, if the expression is false, it will return the first falsey operand. You can read more about Short Circuit Evaluation at the Wikipedia Page.

You have a lot of examples in your question, let's analyze some of them:

>>> False and 1  # return false (short circuited at first falsey value)
False
>>> True and 1   # return 1 (never short circuited and return the last truthy value)
1
>>> 1 and False  # return false (short circuited at first falsey value, in this case the last operand)
False
>>> 1 and True  # return True (never short circuited and return the last truthy value)
True
>>> True and 121  # return 121 (never short circuited and return the last truthy value)
121
>>> False or 1  # return 1 (since the first operand was falsey, or kept looking for a first truthy value which happened to be the last operator)
1
>>> False or 112  # return 112 for same reason as above
112
>>> False or "Khadijah"  # return "Khadijah" for same reason as above
'Khadijah'
>>> True and 'Khadijah'  # return "Khadijah" because same reason as second example
'Khadijah'

I think this should make a point. To help you further understand why this is useful, consider the following example:

You have a function that randomly generate names

import random

def generate_name():
    return random.choice(['John', 'Carl', 'Tiffany'])

and you have a variable that you don't know if it has assigned a name yet so instead of doing:

if var is None:
    var = generate_name()

You can do oneliner:

var = var or generate_name()

Since None is a falsey value, or will continue its search and evaluate second operand, this is, call the function ultimately returning the generated name. This is a very silly example, I have seen better usages (although not in Python) of this kind of style. I couldn't come out with a better example right now. You can also take a look at this questions, there are very useful answers on the topic: Does Python support short-circuiting?

Last but not least, this has nothing to do with static typed, duck typed, dynamic, interpreted, compiled, whatever language. It's just a language feature, one that might come handy and that is very common since almost every programming language I can think of provide this feature.

Hope this helps!

wjandrea
  • 28,235
  • 9
  • 60
  • 81
Paulo Bu
  • 29,294
  • 6
  • 74
  • 73
  • The question was *why* this behaviour exists and I don’t believe you have explained that. – Konrad Rudolph Mar 23 '14 at 23:54
  • @KonradRudolph I'm currently looking for an useful example right now on how to assign with short circuit operators :) – Paulo Bu Mar 23 '14 at 23:55
  • @KonradRudolph Besides, the OP says: _Please help me understand what's going on here, according to the docs this shouldn't happen..._ so I guess he/she was looking for an explanation. – Paulo Bu Mar 23 '14 at 23:56
  • I guess I understand it has to do with the "short circuiting" nature of boolean. And the example was good. I actually see a use for it and I'm sure it'll come up in some code later. – Khadijah Celestine Mar 24 '14 at 03:23
4

One would want and and or to evaluate to an operand (as opposed to always evaluating to True or False) in order to support idioms like the following:

def foo(self):
    # currentfoo might be None, in which case return the default
    return self.currentfoo or self.defaultfoo()

def foobar(self):
    # foo() might return None, in which case return None
    foo = self.foo()
    return foo and foo.bar()

You can of course question the value of such idioms, especially if you aren't used to them. It's always possible to write equivalent code with an explicit if.

As a point against them, they leave some doubt whether the full range of falsey values is possible and intentionally accounted for, or just the one mentioned in the comment (with other falsey values not permitted). But then, this is true in general of code that uses the true-ness of a value that might be something other than True or False. It occasionally but rarely leads to misunderstandings.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • In the case of lambdas, those idioms are indeed the only way of doing certain things. Of course, you can also debate the value of lambdas, so... :) – roippi Mar 24 '14 at 00:20
  • 1
    @roippi: they're not the *only* way, you could write another function for the lambda to call ;-). But yes, for better or worse, lambdas and other contexts that admit a single expression are always a bit like having one hand tied behind your back. So the equivalent code that it's possible to write, could end up a lot more verbose depending what gets disrupted by the change. – Steve Jessop Mar 24 '14 at 00:21
  • @roippi In most cases, `true_result if expression else false_result` might as well be useful. But this is newer than the `and`/`or` stuff and `and`/`or` sometimes are shorter. – glglgl Mar 24 '14 at 19:46
3

This has to do with the way short circuit effect is implemented in Python.

With and (remember True and X = X), the result of the right expression is pushed into the stack, if it's false, it gets poped immediately, else the second expression is poped:

>>> import dis
>>> 
>>> dis.dis(lambda : True and 0)
  1           0 LOAD_CONST               2 (True)
              3 JUMP_IF_FALSE_OR_POP     9
              6 LOAD_CONST               1 (0)
        >>    9 RETURN_VALUE
>>>
>>> True and 0
0
>>> 0 and True
0
>>>

similar to:

def exec_and(obj1, obj2):
    if bool(obj1) != False:
        return obj2
    return obj1

With or, if the first expression is true, it gets popped immediately. If not, the second expression is poped, now the result really depends on the second.

>>> dis.dis(lambda : 0 or False)
  1           0 LOAD_CONST               1 (0)
              3 JUMP_IF_TRUE_OR_POP      9
              6 LOAD_CONST               2 (False)
        >>    9 RETURN_VALUE
>>>
>>> True or 0
True
>>> 1 or False
1
>>> False or 1
1
>>>

similar to:

def exec_or(obj1, obj2):
    if bool(obj1) != True:
        return obj2
    return obj1
S.B
  • 13,077
  • 10
  • 22
  • 49
1

Consider the following use case:

element = dict.has_key('foo') and dict['foo']

Will set element to dict['foo'] if it exists, otherwise False. This is useful when writing a function to return a value or False on failure.

A further use case with or

print element or 'Not found!'

Putting these two lines together would print out dict['foo'] if it exists, otherwise it will print 'Not found!' (I use str() otherwise the or fails when element is 0 (or False) because that s considered falsey and since we are only printing it doesn't matter)

This can be simplified to

print dict.has_key('foo') and str(dict['foo']) or 'Not found!'

And is functionally equivalent to:

if dict.has_key('foo'):
    print dict['foo']
else:
    print 'Not found!'
nettux
  • 5,270
  • 2
  • 23
  • 33