Does the default type.__call__ do more than call _

2019-05-05 18:39发布

问题:

I'm writing a metaclass, and I want an additional method to be called between __new__ and __init__.

If I were calling the method before __new__ or after __init__ I could write e.g.

class Meta(type):
    def __call__(cls):
        ret = type.__call__()
        ret.extraMethod()

My temptation is to write

class Meta(type):
    def __call__(cls):
        ret = cls.__new__(cls)
        ret.extraMethod()
        ret.__init__()
        return ret

and just reproduce the functionality of type.__call__ myself. But I'm afraid there might be some subtlety to type.__call__ I have omitted, which will lead to unexpected behavior when my metaclass is implemented.

I cannot call extraMethod from __init__ or __new__ because I want users of my metaclass to be able to override __init__ and __new__ as in normal Python classes, but to still execute important set-up code in extraMethod.

Thanks!

回答1:

If you really wish to do exactly what you said I can suggest you the following solution:

def call_after(callback, is_method=False):
    def _decorator(func):
        def _func(*args, **kwargs):
            result = func(*args, **kwargs)
            callback_args = (result, ) if is_method else ()
            callback(*callback_args)
            return result
        return _func
    return _decorator


class Meta(type):

    def __new__(mcs, class_name, mro, attributes):
        new_class = super().__new__(mcs, class_name, mro, attributes)
        new_class.__new__ = call_after(
            new_class.custom_method,
            is_method=True
        )(new_class.__new__)
        return new_class


class Example(object, metaclass=Meta):

    def __new__(cls, *args, **kwargs):
        print('new')
        return super().__new__(cls, *args, **kwargs)

    def __init__(self):
        print('init')

    def custom_method(self):
        print('custom_method')


if __name__ == '__main__':
    Example()

This code will generate the following result:

new
custom_method
init