Search code examples
pythonpython-2.7attributesmetaclassdescriptor

Python __super black magic failed


I want to add an attribute for every class created by a metaclass. For example, when a class named C is created, I want add an attribute C._C__sup whose value is the descriptor super(C).

Here is what I've tried:

class Meta(type):
    def __init__(cls, name, bases, dict): # Not overriding type.__new__
        cls.__dict__['_' + name + '__sup'] = super(cls)
        # Not calling type.__init__; do I need it?

class C(object):
    __metaclass__ = Meta

c = C()
print c._C__sup

This gives me:

TypeError: Error when calling the metaclass bases
    'dictproxy' object does not support item assignment

Some background information:
(You don't have to read this part)

Inspired by this article, what I'm doing is trying to avoid "hardcoding" the class name when using super:

The idea there is to use the unbound super objects as private attributes. For instance, in our example, we could define the private attribute __sup in the class C as the unbound super object super(C):

>>> C._C__sup = super(C)

With this definition inside the methods the syntax self.__sup.meth can be used as an alternative to super(C, self).meth. The advantage is that you avoid to repeat the name of the class in the calling syntax, since that name is hidden in the mangling mechanism of private names. The creation of the __sup attributes can be hidden in a metaclass and made automatic. So, all this seems to work: but actually this not the case.


Solution

  • Use setattr instead of assignment to cls.__dict__:

    class Meta(type):
        def __init__(cls, name, bases, clsdict): # Not overriding type.__new__
            setattr(cls, '_' + name + '__sup', super(cls))
            super(Meta, cls).__init__(name, bases, clsdict)
    
    class C(object):
        __metaclass__ = Meta
        def say(self):
            return 'wow'
    
    class D(C):
        def say(self):
            return 'bow' + self.__sup.say()
    
    c = C()
    print(c._C__sup)
    # <super: <class 'C'>, <C object>>
    d = D()
    print(d.say())
    

    prints

    bowwow
    

    By the way, it is a good idea to call

            super(Meta, cls).__init__(name, bases, clsdict)
    

    inside Meta.__init__ to allow Meta to participate in class hierarchies which might need super to properly call a chain of __init__s. This seems particularly appropriate since you are building a metaclass to assist with the use of super.