Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Invoking `super` in classmethod called from metaclass.__new__

I have a case where my class has a custom metaclass, which calls a class method of the class when creating it, something like:

class Metaclass(type):
    def __new__(cls, name, bases, attrs):
        ...
        new_class = super(Metaclass, cls).__new__(cls, name, bases, attrs)
        ...
        new_class.get_fields() # do something
        ...
        return new_class

class FooBar(object):
    __metaclass__ = Metaclass

    @classmethod
    def get_fields(cls):
        ...

(Example of such code is in Tastypie.)

The problem is if I want to do:

class NewBar(FooBar):
    @classmethod
    def get_fields(cls):
        super(NewBar, cls).get_fields()
        ...

This does not work because NewBar is not yet created at the point super is invoked (program flow is still in metaclass). So, is there any workaround?

I know that probably get_fields method could become a method of metaclass, but this would make inheritance much harder to implement (you would have to define both new metaclass and class itself, not nice to developers wanting to extend this classes).

(Python 2.7.)

like image 723
Mitar Avatar asked Oct 06 '12 06:10

Mitar


1 Answers

If NewBar can be unavailable when get_fields is invoked, you can still find it in the MRO of cls:

@classmethod
def get_fields(cls):
    # we can get invoked before NewBar is available in globals,
    # so get NewBar from cls.__mro__
    NewBar = next(c for c in cls.__mro__
                  if c.__module__ == __name__ and c.__name__ == 'NewBar')
    super(NewBar, cls).get_fields()
    ...

Although this code looks funny, it works correctly and is significantly simpler than the alternatives proposed in the question. While most calls to super with a non-constant first argument (such as unqualified super(cls, cls)) are incorrect and break inheritance, this one is safe because the generator expression is nothing but an unconventional way to get a hold of NewBar.

When looking for the clas in the MRO we check for both class and module name (available as __name__, as pointed out by Mitar) to avoid a false positive if othermodule.NewBar inherits from thismodule.NewBar.

like image 190
user4815162342 Avatar answered Nov 04 '22 20:11

user4815162342