2019-08-17
A situation where Python has undefined values
In most of Python, either a name has a value or it doesn't exist
and attempts to access it will fail with some variation of 'that's
not defined'. You get NameError
for globals and AttributeError
for attributes of objects, classes, and interestingly also for
modules. Similarly, accessing a nonexistent key in a dictionary
gets you a KeyError
, also saying that 'this doesn't exist'.
(This means that code inside a module gets a different error for a nonexistent module variable than code outside it. I think this is just an artifact of how the name is accessed.)
But local variables in functions are different and special:
>>> def afunc(): ... print(a) ... a = 10 ... >>> afunc() [...] UnboundLocalError: local variable 'a' referenced before assignment
When we do the print()
, the name a
exists as a local variable
(at least in some sense), but its value is undefined (and an error)
instead of being, say, None
. If a
was not even a local variable,
we should get either some variant of 'name not defined' or we'd
access a global a
if it existed.
(I say that a
exists in some sense because it doesn't fully
exist; for example, it is not in the dictionary that locals()
will return.)
At one level this is a straightforward consequence of how local variables are implemented in CPython. All references to local variables within a function use the same fast access method, whether or not a value has been bound to the local variable. When no value has been set, you get an error.
At another level, this is a sensible language design decision
regardless of the specifics of the implementation. Python has decided
that it has lexically scoped local variables,
and this opens up the possibility of accessing a local variable
before it's had a value set (unlike globals and attributes). When
this happens, you have three choices; you can invent an arbitrary
'unset' value, such as None
, you can generate a 'name does not
exist' error, or you can generate a unique error. Python doesn't
have zero values in the way that a language like Go does (fundamentally
because the meaning of variables is different in the two languages),
so the first choice would be unusual. The second choice would be a
confusing pretense, because the name actually does exist and is in
fact blocking you from accessing a global version of the name. That
leaves the third choice of a unique error, which is at least clear
even if it's unusual.
(This sprung from a Twitter thread.)