The truth about bool in Python
I was trying to modify ayrton
so we could really have sh
1-style file tests.
In sh
, they're defined as unary operators in the -X
form2, where X
is a
letter. For instance, -f foo
returns true (0
in sh-peak) if foo
is some
kind of file. In ayrton
I defined them as functions you could use, but the
names sucked a little. -f
was called _f()
and so on. Part of the reason is,
I think, that both python-sh
and ayrton
already do some -
/_
manipulations
in executable names, and part because I thought that -True
didn't make any
sense.
A couple of days ago I came with the idea that I could symply call the function
f()
and (ab)use the fact that -
is a unary operator. The only detail was to
make sure that -
didn't change the truthiness of bool
s. In fact, it doesn't,
but this surprised me a little, although it shouldn't have:
In [^1]: -True Out[^1]: -1 In [^2]: -False Out[^2]: 0 In [^3]: if -True: print ('yes!') yes! In [^4]: if -False: print ('yes!')
You see, the bool
type was
introduced in Python-2.3
all the way back in 2003. Before that, the concept of true was represented by
any 'true' object, and most of the time as the integer 1
; false was mostly 0
.
In Python-2.2.1, True
and False
were added to the builtins, but only as
other names for 1
and 0
. According the that page and
the PEP, bool
is a subtype of int
so you could still do arithmetic operations like True+1
(!!!), but I'm pretty
sure deep down below the just wanted to be retro compatible.
I have to be honest, I don't like that, or the fact that applying -
to bool
s
convert them to int
s, so I decided to subclass bool
and implement __neg__()
in such a way that it returns the original value. And that's when I got the real
surprise:
In [^5]: class FalseBool (bool): ...: pass ...: TypeError: type 'bool' is not an acceptable base type
Probably you didn't know (I didn't), but Python has such a thing as a 'final class' flag. It can only be used while defining classes in a C extension. It's a strange flag, because most of the classes have to declare it just to be subclassable; it's not even part of the default flags. Even more surprising, is that there are a lot of classes that are not subclassable: around 124 in Python-3.6, and only 84 that are subclassable.
So there you go. You learn something new every day. If you're curious, here's
the final implementation of FalseBool
:
class FalseBool: def __init__ (self, value): if not isinstance (value, bool): raise ValueError self.value= value def __bool__ (self): return self.value def __neg__ (self): return self.value
This will go in ayrton
's next release, which I hope will be soon. I'm also
working in implementing all of the different styles of expansion found in bash
.
I even seem to have found some bugs in it.