why superclass attributes are not available in the current class' namespace?

390 views Asked by At

Example:

class A:
    a = 1

class B(A):
    b = 2
    y = b # works fine
    x = a # NameError: name 'a' is not defined
    x = A.a # works fine

z = B()
z.a # works fine
B.a # works fine

Why is x = a not allowed? In every other context (access through an instance, access through the subclass name) it works fine; but somehow inside the class itself, it doesn't work.

And given this behavior, it seems I cannot implement a class hierarchy where each class defines some additional attributes - since I won't be able to access them in the subclass without knowing exactly where in the hierarchy they are defined.

Here's what I was trying (unsuccessfully) to do:

class X:
  accelerate = compose(f1, f2, f3) # f1, f2, f3 are functions

class Y(X):
  move = compose(f4, f5)
  stop = f6

class Z(Y):
  action = compose(accelerate, stop)

class U(Y):
  action = compose(move, stop)

These classes wouldn't have been initialized at all; I just wanted to use them to create a hierarchy of functions.

3

There are 3 answers

7
Ben On BEST ANSWER

When you write this:

class B(A):
    b = 2
    y = b # works fine
    x = a # NameError: name 'a' is not defined
    x = A.a # works fine

What Python does is create a new scope (stored in a dictionary), execute all your definitions, then at the end of the class block it passes the dictionary to the type class (unless you've set another metaclass) to create your new class. It's roughly equivalent to this:

B = type('B', (A,), classdict_from_scope)

Before you get to that stage, the class B doesn't exist, let alone have any base classes to inherit attributes from. Consider that you could have multiple base classes, and the resolution order of names looked up in those classes is complex and depends on the full set of them and all their bases; this order isn't determined until your child class B is actually created.

This means that when Python comes to execute x = a, it finds a not in scope, and it's not doing an attribute lookup on a class or instance, so it can't follow the name resolution protocol to look for an alternative binding. So all it can do is throw an error.

So that's why Python works that way. What can you do about it?

You have two basic options.

  1. You can specify the class you want to look for the attributes in explicitly.
  2. You can lookup the attributes in your subclass after it is created.

Note that (1) is not that bad if you're only using single inheritance; you can just look up all of the attributes in A; if they're only defined in a parent class of A this will still find them, so you don't actually need to know where in the hierarchy they are defined.

If you have multiple inheritance, then you would have to know at least which hierarchy contained the attribute you wanted to look up. If that's difficult or impossible, then you can use option (2), which would look something like this:

class B(A):
    b = 2
    y = b

B.x = B.a

This looks a little ugly, but constructs an identical class B as you would have if you created x inside the class block, and the transient class B without x can never be seen by any other code if you put the B.x assignment directly after the class block. (It might not have identical results if you're using class decorators, though)

1
jsbueno On

Ben's answer is allright in explaining what is going on.

Since you are working in Python 3, though, there is a feature added to Python's metaclasses that allows what you want to do to be done - A class's local dictionary can be updated (in a non hacky way, like an explicit call to "locals()") before the class body is parsed.

All that is needed is to use the __prepare__ method on the metaclass - it is passed the class name, and its bases as a tuple, and is expected to return the dictionary object that will be used to parse the body class:

class MetaCompose(type):
    @staticmethod
    def __prepare__(name, bases):
        dct = {}
        for base in reversed(bases):
            dct.update(base.__dict__)
        return dct


class A:
    a = 1

class B(A, metaclass=MetaCompose):
    x = a


B.x

(cf. http://docs.python.org/py3k/reference/datamodel.html#customizing-class-creation )

2
Amber On

Class member definitions don't have to be prefixed with the class name (since they're within the class block), but accesses do (because it's unclear what you're wanting to access).

You're able to access b because it's already within the local scope of that block of code (it was defined there). a is not; it's only present after the class is fully defined.

Why do you need to do x = a? Why are you basing the value of one member variable on another? If you really want, you can use the __init__ function of the class to copy the value across (since the subclass is fully defined by the time the __init__ runs).