The logical NOT ( ! ) operator (logical complement, negation) takes truth to falsity and vice versa. It is typically used with boolean (logical) values. When used with non-Boolean values, it returns false if its single operand can be converted to true ; otherwise, returns true .
A Boolean or truth value can be True and False , or, equivalently, the number 1 or 0. For example: False OR True → True. 1 AND 0 → False.
The “NOT” Boolean operator is used to exclude nodes from an audience definition. As it applies to the creation of an audience definition, “NOT” will exclude all users falling under the node which has been prepended by “NOT.”
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:
The only operator returning a boolean value regardless of its operands is the not
operator.
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 emptylist
{}
- an emptydict
()
- an emptytuple
''
- an emptystr
b''
- an emptybytes
set()
- an emptyset
- an empty
range
, likerange(0)
- objects for which
obj.__bool__()
returnsFalse
obj.__len__()
returns0
A "truthy" value will satisfy the check performed by
if
orwhile
statements. We use "truthy" and "falsy" to differentiate from thebool
valuesTrue
andFalse
.
and
WorksWe 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.
or
WorksI 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 over9000
?
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.
Quoting from Python Docs
Note that neither
and
noror
restrict the value and type they return toFalse
andTrue
, but rather return the last evaluated argument. This is sometimes useful, e.g., ifs
is a string that should be replaced by a default value if it is empty, the expressions or 'foo'
yields the desired value.
So, this is how Python was designed to evaluate the boolean expressions and the above documentation gives us an insight of why they did it so.
To get a boolean value just typecast it.
return bool(len(args) and max(args)-min(args))
Short-circuiting.
For example:
2 and 3 # Returns 3 because 2 is Truthy so it has to check 3 too
0 and 3 # Returns 0 because 0 is Falsey and there's no need to check 3 at all
The same goes for or
too, that is, it will return the expression which is Truthy as soon as it finds it, cause evaluating the rest of the expression is redundant.
Instead of returning hardcore True
or False
, Python returns Truthy or Falsey, which are anyway going to evaluate to True
or False
. You could use the expression as is, and it will still work.
To know what's Truthy and Falsey, check Patrick Haugh's answer
and and or perform boolean logic, but they return one of the actual values when they are comparing. When using and, values are evaluated in a boolean context from left to right. 0, '', [], (), {}, and None are false in a boolean context; everything else is true.
If all values are true in a boolean context, and returns the last value.
>>> 2 and 5
5
>>> 2 and 5 and 10
10
If any value is false in a boolean context and returns the first false value.
>>> '' and 5
''
>>> 2 and 0 and 5
0
So the code
return len(args) and max(args)-min(args)
returns the value of max(args)-min(args)
when there is args else it returns len(args)
which is 0.
Is this legit/reliable style, or are there any gotchas on this?
This is legit, it is a short circuit evaluation where the last value is returned.
You provide a good example. The function will return 0
if no arguments are passed, and the code doesn't have to check for a special case of no arguments passed.
Another way to use this, is to default None arguments to a mutable primitive, like an empty list:
def fn(alist=None):
alist = alist or []
....
If some non-truthy value is passed to alist
it defaults to an empty list, handy way to avoid an if
statement and the mutable default argument pitfall
Yes, there are a few gotchas.
fn() == fn(3) == fn(4, 4)
First, if fn
returns 0
, you cannot know if it was called without any parameter, with one parameter or with multiple, equal parameters :
>>> fn()
0
>>> fn(3)
0
>>> fn(3, 3, 3)
0
fn
mean?Then, Python is a dynamic language. It's not specified anywhere what fn
does, what its input should be and what its output should look like. Therefore, it's really important to name the function correctly. Similarly, arguments don't have to be called args
. delta(*numbers)
or calculate_range(*numbers)
might describe better what the function is supposed to do.
Finally, the logical and
operator is supposed to prevent the function to fail if called without any argument. It still fails if some argument isn't a number, though:
>>> fn('1')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in fn
TypeError: unsupported operand type(s) for -: 'str' and 'str'
>>> fn(1, '2')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in fn
TypeError: '>' not supported between instances of 'str' and 'int'
>>> fn('a', 'b')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in fn
TypeError: unsupported operand type(s) for -: 'str' and 'str'
Here's a way to write the function according to the "Easier to ask for forgiveness than permission." principle:
def delta(*numbers):
try:
return max(numbers) - min(numbers)
except TypeError:
raise ValueError("delta should only be called with numerical arguments") from None
except ValueError:
raise ValueError("delta should be called with at least one numerical argument") from None
As an example:
>>> delta()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in delta
ValueError: delta should be called with at least one numerical argument
>>> delta(3)
0
>>> delta('a')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in delta
ValueError: delta should only be called with numerical arguments
>>> delta('a', 'b')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in delta
ValueError: delta should only be called with numerical arguments
>>> delta('a', 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in delta
ValueError: delta should only be called with numerical arguments
>>> delta(3, 4.5)
1.5
>>> delta(3, 5, 7, 2)
5
If you really don't want to raise an exception when delta
is called without any argument, you could return some value which cannot be possible otherwise (e.g. -1
or None
):
>>> def delta(*numbers):
... try:
... return max(numbers) - min(numbers)
... except TypeError:
... raise ValueError("delta should only be called with numerical arguments") from None
... except ValueError:
... return -1 # or None
...
>>>
>>> delta()
-1
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With