2

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.

Charles
  • 50,943
  • 13
  • 104
  • 142
max
  • 49,282
  • 56
  • 208
  • 355
  • here a is not a global variable it can call with its class name in which it define. – Code Lღver Jan 25 '12 at 06:16
  • @max: I just found out that doing `locals().update(A)` inside the class body works around for you to get what you want. (Put it as the first line of your class B in your example code) - Not posting as an answer, because @Ben's is the correct answer here. – jsbueno Jan 25 '12 at 10:59

3 Answers3

7

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)

Ben
  • 68,572
  • 20
  • 126
  • 174
  • Thanks. One problem, even with the single inheritance, is that I'd *prefer* to say `B.a` rather than `A.a` because one day I might want to override parent's `a` inside `B`. Unfortunately, it seems impossible. I guess a class hierarchy isn't intended to be used like this.. It feels like defining my functions as function objects rather than methods isn't very compatible with Python. – max Jan 25 '12 at 07:01
  • @max: Having a lot of complex computation on class variables in class blocks is a bit odd. You normally do that sort of thing in instances of your classes. I personally promote the use of functions rather than methods; I see lots of code using classes when the problem doesn't really look very class-like at all. How is this interfering with your use of functions? – Ben Jan 25 '12 at 07:09
  • I do have a lot of highly reusable functions. They are often composed with each other. Some functions actually operate on other functions. I simply wanted to arrange them into a hierarchy, so I would know which functions to use in each scenario. Since many functions have the same theme, I wanted to put them in one class. And then some more specific scenarios would be modelled by subclasses. Except it seems putting functions in the class attributes like doesn't work well. – max Jan 25 '12 at 07:17
  • @max: Yeah, it sounds like classes aren't actually a very good fit for what you need. You seem to be using them partly as namespaces, partly as a convenient defaulting mechanism, but not really as the definition of things-in-common amongst a set of objects (the instances of the class), which is what the concept of a class actually is. Given how much trouble you're having (and how much more trouble I suspect you'll have with Python trying to interpret your functions as methods), I think possibly either modules, or dictionaries of functions might be a better fit for your needs. – Ben Jan 25 '12 at 07:23
  • Another alternative would be to define *one* class that's designed to be instantiated with functions in keyword arguments and other objects to defer to in positional arguments, and implement "instance methods" (i.e. functions as attributes of the instance rather than the class). With some fiddling around with `__getattr__` you *should* be able to get the defaulting magic to mostly just work. But you'd have to be invoking these functions as methods on some object, which might break your `compose` stuff; I'm not sure. – Ben Jan 25 '12 at 07:28
  • Precisely - I need a convenient defaulting mechanism; I don't have any instances to represent. So it makes sense that classes aren't intended for that; I'll look into using modules and dictionaries. The class approach you suggest sounds very interesting, but I'm afraid I'll die from its complexity. – max Jan 25 '12 at 07:29
  • I ended up using modules instead of classes, as you suggest. And while it does have some limitations (I can't ever attach any state to them as I could to instances), it works fine for my purposes. – max Feb 06 '12 at 10:30
2

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).

Amber
  • 507,862
  • 82
  • 626
  • 550
  • 1
    You are right about doing assignment in `__init__`, but still `x=a` or `self.x = a` won't work. You still have to do `self.x = self.a`. I guess the OP is pointing to that. However accessing `self.a` solves the problem of knowing the definer class in a class hierarchy. – 0xc0de Jan 25 '12 at 06:44
  • Yes, that was what I meant - using `__init__`, you can always use `self.` without having to know the hierarchy. – Amber Jan 25 '12 at 09:33
1

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 )

jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • Due to the comments in other answers, I'm no longer sure I'll do what I originally asked for. But if I do, it's a perfect solution. – max Feb 06 '12 at 10:28