8

In a recent project I try to do something like this (more complex, but the same result):

class MetaA(type):
    def __new__(cls, name, args, kwargs):
        print kwargs["foo"]
        return type.__new__(cls, name, args, kwargs)

class A(object):
    __metaclass__ = MetaA
    foo = "bar"

class B(A):
    pass

I get this:

bar
Traceback (most recent call last):
  File "C:\Users\Thorstein\Desktop\test.py", line 10, in <module>
    class B(A):
  File "C:\Users\Thorstein\Desktop\test.py", line 3, in __new__
    print kwargs["foo"]
KeyError: 'foo'

Are class attributes not inherited? If so, is there any workaround possible in a similar framework to the above?

EDIT: Might be easier to see what I mean using an actual (simplified) example from the program..

class GameObjectMeta(type):
    def __new__(cls, name, bases, attrs):
        attrs["dark_color"] = darken(*attrs["color"])
        return type.__new__(cls, name, bases, attrs)


class GameObject(object):
    __metaclass__ = GameObjectMeta
    color = (255,255,255)   # RGB tuple

class Monster(GameObject):
    pass

Basically, want to run a function on the base color to make a darker one that's saved in the class (multiple instances of the class will want the same color, but there will be a longer class hierarchy). I hope this'll make more sense..

thrstein
  • 83
  • 1
  • 5

2 Answers2

4

It's not supposed to inherit those. The metaclass receives the attributes defined on the class it is instantiating, not those of its base classes. The whole point of the metaclass constructor is to get access to what is actually given in the class body.

In fact, the attributes of the base class really aren't "in" the subclass at all. It's not as if they are "copied" to the subclass when you define it, and in fact at creation time the subclass doesn't even "know" what attributes its superclasses might have. Rather, when you access B.foo, Python first tries to find the attribute on B, then if it doesn't find it, looks on its superclasses. This happens dynamically every time you try to read such an attribute. The metaclass mechanism isn't supposed to have access to attributes of the superclass, because those really aren't there on the subclass.

A perhaps related problem is that your definition of __new__ is incorrect. The arguments to the metaclass __new__ are cls, name, bases, and attrs. The third argument is the list of base classes, not "args" (whatever you intend that to be). The fourth argument is the list of attributes define in the class body. If you want access to inherited attributes you can get at them via the bases.

Anyway, what are you trying to accomplish with this scheme? There's probably a better way to do it.

BrenBarn
  • 242,874
  • 37
  • 412
  • 384
  • Thank you, seems like I was confused with the `__new__` arguments. I think I could find a workaround to my problem using that. What I'm trying to accomplish is accessing an attribute to create a second one (just using the above example, doing `kwargs["cheese"] = kwargs["foo"]+"bar"`). Basically giving the option to modify the "foo" attribute in a subclass, and "cheese" always behaving the same way, using the subclass value or the default parent class value. I'm using python class definitions in place of data files (django style), so I want the classes as easy to write as possible. – thrstein Sep 08 '12 at 08:29
  • Probably reading [this](https://fuhm.org/super-harmful/) article may help understanding that when you say "looks on its superclasses", this isn't often done how we would expect it. Even though this happens only when multiple-inheritance is used. – Bakuriu Sep 08 '12 at 08:38
  • After some headscratching based on your response, I figured it out (stupid as I am). I check if the `color` attribute is present in the subclass, if not I don't worry about it. It'll then inherit both `color` and `dark_color` from the parent. I appreciate your help! – thrstein Sep 08 '12 at 09:13
  • This really clears up both the attributes dictionary passed to the metaclass `__new__` or `__init__` methods and that base classes are not considered when modules are imported and memory is allocated for classes by calling their metaclasses' `__new__` methods (and then perhaps those class objects are initialized during the metaclasses' `__init__` method). This answer also clears up how base class attributes are called from subclasses dynamically at run time and not during class creation when the module is imported. The post would benefit from references to back up these important facts. – Mark Mikofski Nov 20 '13 at 07:26
0

If you use the __init__ instead of __new__ then you can inherit the base class attributes:

class MetaA(type):
    def __init__(self, name, bases, attr):
        print self.foo  # don't use attr because foo only exists for A not B
        print attr  # here is the proof that class creation does not follow base class attributes
        super(MetaA, self).__init__(name, base, attr)  # this line calls type(), but has no effect

class A(object):
    __metaclass__ = MetaA
    foo = 'bar'

class B(A):
    pass

when A is created returns:

bar
{'__module__': '__main__', 'foo': 'bar', '__metaclass__': <class '__main__.MetaA'>}

when B is created returns:

bar
{'__module__': '__main__'}

Note, and this is what @BrenBarn was saying in his answer, that foo is not in attr when B is created. That's why the code in OP question raises KeyError. However calling self.foo looks for foo in B then if it can't find it looks in its bases, IE A.

The metaclass for both classes can be determined by looking at its class:

>>> print A.__class__
<class '__main__.MetaA'>
>>> print A.__class__
<class '__main__.MetaA'>

The fact that the metaclass __init__ method is called when allocating and initializing B, printing self.foo and attr in stdout also proves that B's meta class is MetaA.

Community
  • 1
  • 1
Mark Mikofski
  • 19,398
  • 2
  • 57
  • 90