How do "and" and "or" act with non-boolean values?

logical-operators, operators, python

Solution

TL;DR

We start by summarising the two behaviour of the two logical operators `and` and `or`. These idioms will form the basis of our discussion below.

`and`

Return the first Falsy value if there are any, else return the last value in the expression.

`or`

Return the first Truthy value if there are any, else return the last value in the expression.

The behaviour is also summarised in the docs, especially in this table:

Operation Result

`x or y` if x is false, then y, else x

`x and y` if x is false, then x, else y

`not x` if x is false, then `True`, else `False`

The only operator returning a boolean value regardless of its operands is the `not` operator.

"Truthiness", and "Truthy" Evaluations

The statement

len(args) and max(args) - min(args)

Is a very pythonic concise (and arguably less readable) way of saying "if `args` is not empty, return the result of `max(args) - min(args)`", otherwise return `0`. In general, it is a more concise representation of an `if-else` expression. For example,

exp1 and exp2

Should (roughly) translate to:

r1 = exp1
if r1:
    r1 = exp2

Or, equivalently,

r1 = exp2 if exp1 else exp1

Similarly,

exp1 or exp2

Should (roughly) translate to:

r1 = exp1
if not r1:
    r1 = exp2

Or, equivalently,

r1 = exp1 if exp1 else exp2

Where `exp1` and `exp2` are arbitrary python objects, or expressions that return some object. The key to understanding the uses of the logical `and` and `or` operators here is understanding that they are not restricted to operating on, or returning boolean values. Any object with a truthiness value can be tested here. This includes `int`, `str`, `list`, `dict`, `tuple`, `set`, `NoneType`, and user defined objects. Short circuiting rules still apply as well.

But what is truthiness? It refers to how objects are evaluated when used in conditional expressions. @Patrick Haugh summarises truthiness nicely in this post.

All values are considered "truthy" except for the following, which are "falsy":

- `None`

- `False`

- `0`

- `0.0`

- `0j`

- `Decimal(0)`

- `Fraction(0, 1)`

- `[]` - an empty `list`

- `{}` - an empty `dict`

- `()` - an empty `tuple`

- `''` - an empty `str`

- `b''` - an empty `bytes`

- `set()` - an empty `set`

- an empty `range`, like `range(0)`

- objects for which

- `obj.__bool__()` returns `False`

- `obj.__len__()` returns `0`

A "truthy" value will satisfy the check performed by `if` or `while` statements. We use "truthy" and "falsy" to differentiate from the `bool` values `True` and `False`.

How `and` Works

We build on OP's question as a segue into a discussion on how these operators in these instances.

Given a function with the definition

def foo(*args):
    ...

How do I return the difference between the minimum and maximum value in a list of zero or more arguments?

Finding the minimum and maximum is easy (use the inbuilt functions!). The only snag here is appropriately handling the corner case where the argument list could be empty (for example, calling `foo()`). We can do both in a single line thanks to the `and` operator:

def foo(*args):
     return len(args) and max(args) - min(args)
foo(1, 2, 3, 4, 5)
# 4

foo()
# 0

Since `and` is used, the second expression must also be evaluated if the first is `True`. Note that, if the first expression is evaluated to be truthy, the return value is always the result of the second expression. If the first expression is evaluated to be Falsy, then the result returned is the result of the first expression.

In the function above, If `foo` receives one or more arguments, `len(args)` is greater than `0` (a positive number), so the result returned is `max(args) - min(args)`. OTOH, if no arguments are passed, `len(args)` is `0` which is Falsy, and `0` is returned.

Note that an alternative way to write this function would be:

def foo(*args):
    if not len(args):
        return 0
    
    return max(args) - min(args)

Or, more concisely,

def foo(*args):
    return 0 if not args else max(args) - min(args)

If course, none of these functions perform any type checking, so unless you completely trust the input provided, do not rely on the simplicity of these constructs.

How `or` Works

I explain the working of `or` in a similar fashion with a contrived example.

Given a function with the definition

def foo(*args):
    ...

How would you complete `foo` to return all numbers over `9000`?

We use `or` to handle the corner case here. We define `foo` as:

def foo(*args):
     return [x for x in args if x > 9000] or 'No number over 9000!'

foo(9004, 1, 2, 500)
# [9004]

foo(1, 2, 3, 4)
# 'No number over 9000!'

`foo` performs a filtration on the list to retain all numbers over `9000`. If there exist any such numbers, the result of the list comprehension is a non-empty list which is Truthy, so it is returned (short circuiting in action here). If there exist no such numbers, then the result of the list comp is `[]` which is Falsy. So the second expression is now evaluated (a non-empty string) and is returned.

Using conditionals, we could re-write this function as,

def foo(*args):
    r = [x for x in args if x > 9000]
    if not r:
        return 'No number over 9000!' 
    
    return r

As before, this structure is more flexible in terms of error handling.

Problem

I'm trying to learn python and came across some code that is nice and short but doesn't totally make sense the context was: ``` def fn(*args): return len(args) and max(args)-min(args) ``` I get what it's doing, but why does python do this - ie return the value rather than True/False? ``` 10 and 7-2 ``` returns 5. Similarly, changing the and to or will result in a change in functionality. So ``` 10 or 7 - 2 ``` Would return 10. Is this legit/reliable style, or are there any gotchas on this?

Original source

Related problems