Peculiar Python 1
Disclaimer: The title sounds clickbait-y but I used it because I like alliterations. The behaviour of code described here is not peculiar, it is exactly as expected from the specifications. However, it is not something that we come across day to day.
Not very long ago, I got a code snippet from Mustaque, a brilliant friend and a colleague of mine, and a question whether I can explain why the code is behaving in the way it is. (I still suspect he knew the answer and was just messing with me). This post is my answer to the question.
Here is the above-mentioned snippet:
~ % python3
>>> NotImplemented and True
<stdin>:1: DeprecationWarning: NotImplemented should not be used in a boolean context
True
>>> NotImplemented = True
False
>>> ... and True
True
>>> ... and False
False
>>> True and ...
Ellipsis
>>> False and ...
False
Breaking down what we see:
- There were two not-so-normal constants here:
NotImplemented
and... (Ellipsis)
- We are doing boolean operations on them, and we see that they are truthy i.e., they evaluate to true in a boolean expression.
- The return value differs based on the order of the operators in the boolean operations.
What is NotImplemented?
NotImplemented is a built-in constant i.e., a special value in Python. We use it when we are defining the binary special methods of a class (e.g.__eq__()
, __lt__()
, __add__()
, __rsub__()
, __imul__()
, __iand__()
, etc.) to let the interpreter know that the operation is not implemented with the other type.
Let me clarify what I’m talking about with an example:
class Coords:
def __init__(self, x:int, y:int) -> None:
self.x = x
self.y = y
def __lt__(self, other):
if not isinstance(other, Coords):
return NotImplemented
return self.x < other.x and self.y < other.y
a = Coords(1,2)
b = Coords(3,4)
print(a<b)
print(b<a)
print(a<2)
Output of the above snippet:
True
False
Traceback (most recent call last):
File "~/Code-Shown-In-Blog/Peculiar Python/1.py", line 17, in <module>
print(a<2)
^^^
TypeError: '<' not supported between instances of 'Coords' and 'int'
As you can see, I use NotImplemented
to tell the interpreter that I have not implemented comparison between Coords
and any other type.
What is …?
…
, which evaluates to Ellipsis is another built-in constant in Python. It has no single expected behaviour, but is used in multiple contexts:
- As a replacement for
pass
when defining yet to be implemented functions.
def func():
...
- Some uses in typing which I have not personally used/explored.
- Some third party libraries use it to denote some custom behaviour. Ex: Pydantic uses it to denote a field which is required but can be set to
None
Why are NotImplemented and Ellipsis Truthy?
They are truthy because they are not falsy. Now that you are slightly annoyed, I’ll explain it better.
In Python, any object can be tested for truth value, and by default it considers all objects to be true unless its class defines a __bool()__
which returns False
or __len()__
which returns 0. Below is the list of built-in constants which are considered falsy.
- constants defined to be false:
None
andFalse
- zero of any numeric type:
0
,0.0
,0j
,Decimal(0)
,Fraction(0, 1)
- empty sequences and collections:
''
,()
,[]
,{}
,set()
,range(0)
Since, NotImplemented
and Ellipsis
do not meet the criteria for falsiness, they are truthy. (NotImplemented will soon become falsy, linking the discussion and PR detailing why)
Why does the Order of Operators decide the Return Value in Boolean Operations?
You’ll hate me for this but it because that’s how it is supposed to be. As defined in the Python docs, the boolean operators work the following way:
Operation | Result |
---|---|
x or y |
if x is true, then x, else y |
x and y |
if x is false, then x, else y |
not x |
if x is false, then True , else False |
So, the return value is based on the above logic.
I have explained the code snippet to the best of sleep-deprived abilities, please feel free to talk to me if you have feedback or comments. As the dolphins say, So long, and thanks for all the fish.