Mitar
Mitar

Reputation: 7030

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

Upvotes: 8

Views: 1673

Answers (3)

Sherpa
Sherpa

Reputation: 1998

I know that the question is specific to python 2.7, however, for those using python 3.6 you can simply call super().

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

Upvotes: 0

user4815162342
user4815162342

Reputation: 155046

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.

Upvotes: 2

Mitar
Mitar

Reputation: 7030

Based on answer from @user4815162342 I found even simpler solution:

try:
    super(NewBar, cls).get_fields()
except NameError, e:
    if 'NewBar' in str(e):
        super(cls, cls).get_fields()
    else:
        raise

Upvotes: 0

Related Questions