Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python metaclass inheritance issue

I have a somewhat bizarre metaclass question. I'm using a meta class to dynamically create a 'sibling' class that inherits from another superclass and assign it as an attribute on the original class. Below is a minimal setup:

class Meta(type):
def __new__(cls, name, parents, dct):
    sdct = dct.copy()
    dct['sibling'] = type(name+'Sibling', (Mom,), sdct)
    return super().__new__(cls, name, (Dad,), dct)

class Mom:
     def __init__(self):
         self.x = 3

class Dad:
     def __init__(self):
         self.x = 4

class Child(metaclass=Meta):
     def __init__(self):
         super().__init__()
         self.y = 1  # <<< added from feedback
print(Child().x) # 4
print(Child().sibling) # <class '__main__.Child'> | should be ChildSibling
print(Child().sibling().x) # should be 3 instead throws:
    # TypeError: super(type, obj): obj must be an instance or subtype of type
print(Child().sibling().y) # should print 4

Something seems to be going wrong above with the creation of the 'sibling' class but I'm not quite sure what. I know for example that this would work:

class ChildAbstract:
    def __init__(self):
        super().__init__()

ChildSibling = type('ChildSibling', (ChildAbstract, Mom), {})
Child = type('Child', (ChildAbstract, Dad), {'sibling': ChildSibling})
print(Child().sibling().x) # 3

I can't see the difference between the two cases though.

like image 490
Buck Avatar asked Mar 30 '16 20:03

Buck


1 Answers

The dictionary sdct passed to type includes __qualname__, which according to this PEP is what repr and str now use.

Try adding

print(Child is Child.sibling)  # False
print(Child.sibling.__name__)  # "ChildSibling"

and you'll see that it is truly the sibling.

As to why sibling().x throws, the very same sdct also already contains Child.__init__, which ends up as __init__ of your dynamically created new type ChildSibling. During a call to sibling() the super() call resolves the class to Child and is given an instance of ChildSibling:

Also note that, aside from the zero argument form, super() is not limited to use inside methods. The two argument form specifies the arguments exactly and makes the appropriate references. The zero argument form only works inside a class definition, as the compiler fills in the necessary details to correctly retrieve the class being defined, as well as accessing the current instance for ordinary methods.

https://docs.python.org/3/library/functions.html#super

Accessing the current instance is done by passing the first argument to method as instance.

super() -> same as super(__class__, <first argument>)

The error is raised at line 7210 of Object/typeobject.c.

Try removing the wrong __init__ in your __new__ with:

del sdct['__init__']

and now

print(Child().sibling().x)

will print 3.

A solution to "generic" inheritance and meta programming friendlier __init__ is to use the 2 argument form of super():

def __init__(self):
    super(self.__class__, self).__init__()
like image 185
Ilja Everilä Avatar answered Sep 21 '22 17:09

Ilja Everilä