The scope of names defined in class block doesn't extend to the methods' blocks. Why is that?

Question:

Reading the documentation I came across the following paragraph:

A scope defines the visibility of a name within a block. If a local
variable is defined in a block, its scope includes that block. If the
definition occurs in a function block, the scope extends to any blocks
contained within the defining one, unless a contained block introduces
a different binding for the name. The scope of names defined in a
class block is limited to the class block; it does not extend to the
code blocks of methods
– this includes comprehensions and generator
expressions since they are implemented using a function scope.

I decided to try accessing class variable from a method myself:

>>> class A():
    i = 1
    def f(self):
        print(i)            

>>> a = A()

>>> a.i
1

>>> a.f()
Traceback (most recent call last):
  File "<pyshell#7>", line 1, in <module>
    a.f()
  File "<pyshell#4>", line 4, in f
    print(i)
NameError: global name 'i' is not defined

I know that the variable i may be accessed by explicitly pointing to the class name A.i:

>>> a = A()
>>> class A():
    i = 1
    def f(self):
        print(A.i)          
>>> a = A()
>>> a.f()
1

The question is why the developers of the language made class variables not visible from methods? What is the rationale behind it?

Asked By: ovgolovin

||

Answers:

This seems to be related to the use of an explicit self parameter, and the requirement that all method calls and instance attribute accesses explicitly use self. It would be at least strange if the uncommon case of accessing a class scope function as a normal function would be much easier than the common case of accessing it as a method via self. Class variables are usually also accessed via the instance in Python.

In C++, in contrast, the class scope is visibile in all methods, but calling a method implicitly passes this. This seems to be the other sane choice.

Answered By: Sven Marnach

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:

  1. 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.
  2. 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).
  3. 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.

Answered By: Ben
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.