python's pre-declared constants are kinda weird

57 points by seb 12 hours ago on lobsters | 12 comments

hongminhee | 10 hours ago

builtins.True and friends are a fossil of pre-3.0 history. True and False started out as ordinary, reassignable builtins under PEP 285, landing first in 2.2.1 as a bugfix-release backport and then formalized properly in 2.3. None got its reassignment blocked in 2.4. True and False stayed assignable through the rest of the 2.x line and only became real keywords in 3.0.

The builtins slot is just what's left of that old namespace, kept around mostly so getattr/dir/vars introspection still works on them. It also explains why setattr(builtins, 'True', 67) doesn't touch the literal: the keyword compiles to a constant baked into the bytecode at compile time, separate from whatever's sitting in the builtins namespace.

andyfriesen | an hour ago

As I understand it, True = False was a perfectly reasonable thing to do in Python 2.x, which means that the interpreter had to actually look up the True and False objects each time you referred to them!

chrismorgan | 50 minutes ago

It was never reasonable. Possible perhaps, but not reasonable.

tclancy | 32 minutes ago

A perfectly reasonable thing to do as a prank, yeah.

mitsuhiko | 9 hours ago

an interesting side effect of this is that expressions like x.True raise a SyntaxError. i'm curious as to what the rationale was for this decision (if there was one).

A lot of things in Python can be explained by just how the language "design" works. The interpreter at one point did not have bools, they were just aliases to 1 and 0 which is why they are still integers today. However at one point someone on the way to Python 3 benchmarked the thing and realized that issuing LOAD_GLOBAL for these common constants is wasteful and they were reserved and compiled into bytecode instructions.

Ellipsis gained a whole token in Python 3 which is .... Previously that was only possible in certain subscript positions. So it's similar, just not by its name. NotImplemented is rare enough that there is really no point in making it a keyword. It's just a marker constant for comparisons.

chrismorgan | 8 hours ago

which is why they are still integers today

I don’t understand what you mean, since getattr(builtins, "True") is True and type(True) is bool and bool is not int.

pyfisch | 6 hours ago

This means you can do math with bools, like you would with integers:

>>> (True + True) ** (True + True + True)
8

chrismorgan | 51 minutes ago

I quibble: that doesn’t require that booleans be integers; a suitable __add__ method will do. And you probably want a def __int__(self): return 1 if self is True else 0 so that int(True) works. And before long you’ll have defined all other numerical methods, since this isn’t JavaScript where (true + true) ** (true + true + true) == 8 without booleans being integers because it’s eager to convert types. And… once you’ve defined __add__, __mul__, __div__, &c… well, duck typing doctrine will say it’s an integer after all. Ah well.

Try isinstance(True, int) or True == 1

chrismorgan | 8 hours ago

Oh yeah, bool.__mro__ == (bool, int, object). Completely forgot about that.

(True == 1 isn’t proof because of __eq__.)

PuercoPop | 3 hours ago

Besides all the observable behaviour, like addition being defined or 1 / False raising ZeroDivisionError`. You can look at the code to see that boolean's are defined as 0 and 1 here:

https://github.com/python/cpython/blob/9f8780022058e629dada82221c384b67c5687b2e/Objects/boolobject.c#L215-L226

You can also see that the the base type (tp_base) of PyBool_Type is defined as long (PyLong_Type)

Slop language