Today I read Giedrius Statkevičius' What is Actually True and False in Python? ( via ), which talks about the history of how
True
and
False
aren't fixed constants until Python 3 and thus how you can change them in Python 2. But what does it really mean to do this? So let's dive right in to the details in an interactive Python 2 session.
As seen in Statkevičius' article, reversing
True
and
False
is pretty straightforward:
>>> int(True) 1 >>> True, False = False, True >>> int(True) 0
Does this change what boolean comparisons actually return, though?
>>> int((0 == 0) == True) 0 >>> (0 == 0) == True False >>> (0 == 0) == False True >>> (0 == 0) is False True
It doesn't, and this is our first clue to what is going on. We haven't changed the Python interpreter's view of what True and False are, or the actual
bool
objects that are True and False; we've simply changed what the names
True
and
False
refer to. Basically we've done '
fred, barney = False, True
' but (re)using names that code expects to have a certain meaning. Our subsequent code is using our redefined
True
and
False
names because Python looks up what names mean dynamically, as the code runs, so if you rebind a name that rebinding takes immediate effect.
This is also why the truth values being printed are correct; the
bool
objects themselves are printing out their truth value, and since that truth value hasn't changed we get the results we expect:
>>> True, False (False, True)
But what names have we changed?
>>> (0 == 0) is __builtins__.True True >>> True is __builtins__.False True >>> globals()["True"] False
This tells us the answer, which is that we've added
True
and
False
global variables in our module's namespace by copying
False
and
True
values from the global builtins. This means that our redefined
True
and
False
are only visible in our own namespace. Code in other modules will be unaffected, as we've only shadowed the builtin names inside our own module.
(An interactive Python session has its own little module-level namespace.)
To see that this is true, we need a
tst
helper module with a single function:
def istrue(val):
if val == True:
print "Yes"
else:
print "No"
Then:
>>> import tst >>> tst.istrue(True) No >>> tst.istrue(0 == 0) Yes
But we don't have to restrict ourselves to just our own module. So let's redefine the builtin versions instead, which will have a global effect. First, let's clear out our 'module' versions of those names:
>>> del True; del False
Then redefine them globally:
>>> __builtins__.True, __builtins__.False = (0 == 1), (0 == 0) >>> (0 == 0) is True False
We can verify that these are no longer in our own namespace:
>>> globals()["True"] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'True'
We reuse our helper module to show that we've now made a global change:
>>> tst.istrue(0 == 0) No
But of course:
>>> tst.istrue(True) Yes
Changing
__builtins__.True
has changed the
True
that all modules see, unless they deliberately shadow the builtin
True
with their own module-level
True
. Unlike before,
True
now means the same thing in our interactive session and in the
tst
module.
Since modules are mutable, we can actually fix
tst.istrue
from the outside:
>>> tst.True = (0 == 0) >>> tst.istrue(0 == 0) Yes >>> tst.True True
Now the
tst
module has its own module-global
True
name with the correct value and
tst.istrue
works correctly again. However, we're back to a difference in what
True
means in different modules:
>>> tst.istrue(True) No >>> False is tst.True True
(Since our interactive session's 'module' has no name binding for
False
, it uses the binding in the builtins, which we made point to the True boolean object. However
tst
has its own name binding for
True
, which also points to the True boolean object. Hence our
False
is
tst
's
True
. Yes, this gets confusing fast.)
As noted in Statkevičius' article, Python only ever has two
bool
objects, one True and one False. These objects are immutable (and known by the CPython interpreter), and so we can't change the actual truth value of comparisons, what gets printed by the
bool
objects, and so on. All we can do is change what the names
True
and
False
mean at various levels; in a function (not shown here), for an entire module, or globally through the builtins.
(Technically there's a few more namespaces we could fiddle with.)
As a side note, we can't subclass
bool
to make a thing that is considered a boolean yet has different behavior. If we try it, CPython 2 tells us:
TypeError: Error when calling the metaclass bases
type 'bool' is not an acceptable base type
This is an explicitly coded restriction; the C-level
bool
type doesn't allow itself to be subclassed.
(Technically it's coded by omitting a 'this can be a base type' flag from the C-level type flags for the
bool
type, but close enough. There are a number of built-in CPython types that can't be subclassed because they omit this flag.)
We can change the
True
and
False
names to point to non-
bool
objects if we want. If you take this far enough, you can arrange to get interesting errors and perhaps spectacular explosions:
>>> __builtins__.False = set("a joke")
>>> (0 != 0) == False
False
>>> d = {}
>>> d[False] = False
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'set'
For maximum fun, arrange for
True
and
False
to be objects that are deliberately uncomparable and can't be converted to booleans (in Python 2, this requires raising an error in your
__eq__
and
__nonzero__
methods).
(I've used
False
here because many objects in Python 2 are considered to be boolean True. In fact, by default almost all objects are; you have to go out of your way to make something False.)