A class block is syntactic sugar for building a dictionary, which is then passed to the metaclass (usually type
) to construct the class object.
class A:
i = 1
def f(self):
print(i)
Is roughly equivalent to:
def f(self):
print(i)
attributes = {'f': f, 'i': 1)
A = type('A', (object,) attributes)
Seen that way, there is no outer scope the i
name to come from. However there obviously is a temporary scope for you to execute the statements in the class block. It would be possible for that class block to desugar to something more like:
def attributes():
i = 1
def f(self):
print(i)
return locals()
A = type('A', (object,), attributes())
In this case the outer reference to i
would work. However, this would be going "against the grain" of Python's object system philosophy.
Python has objects, which contain attributes. There's not really any concept of "variables" other than local variables in functions (which can be nested to create a scope chain). A bare name is looked up as a local variable, then in outer scopes (which come from functions). Attributes are looked up, using the dotted name syntax, on other objects, and you always specify which object to look in.
There is a protocol for resolving attribute references, which says that when attribute
is not found on obj
, obj.attribute
can be resolved by looking in the class of obj
(and its base classes, using the method resolution order). This is actually how methods are found; when in your example you executed a.f()
, the a
object contains no attribute f
, so the class of a
(which is A
) is searched, and the method definition is found.
Having class attributes automatically available in an outer scope for all methods would be weird, because no other attribute works this way. It would also have the following drawbacks:
- Functions defined outside the class and assigned to it later would have to use different syntax to refer to the class attribute than functions defined as part of a class.
- Because it's shorter, it would encourage reference to class attributes including staticmethods and classmethods as bare names:
thing
rather than using Class.thing
or self.thing
. This makes them look like module globals when they're not (method definitions are usually short enough that you can easily see they're not defined locally).
- Note that looking for the attributes on
self
allows them to play nicer with subclasses, as it allows subclasses to override the attribute. That probably isn't as big a deal for "class constants", but it's very important for staticmethods and classmethods.
Those are the main reasons I see, but ultimately it's just a choice the designers of Python made. You find it weird that you don't have this implicit ability to reference class variables, but I find implicit class and instance variable access in languages like C++ and Java to be weird. Different people have different opinions.