0

Consider that code:

class Meta(type):
    def __init__(cls, name, bases, attrs):
        def method(self):
            print('generated method call')
        cls.method = method
        super(Meta, cls).__init__(name, bases, attrs)


class A(object):
    __metaclass__ = Meta

    def method(self):
        raise NotImplementedError


def decorator(fn):
    def wrapper(*args, **kwargs):
        print('decorator call')
        return fn(*args, **kwargs)

    return wrapper

class Decorator(object):
    """Mysterious mixin that should wrap `method` with `decorator`
    at class generation time. And this effect should work on subclasses too!
    """
    def __call__(self, cls):
        cls.method = decorator(cls.method)
        return cls

@Decorator()
class B(A):
    pass

B().method() # outputs "decorated call generated method call"

class D(B):
    pass

D().method() # outputs only "generated method call"

Here I have a base class A, that has a metaclass that generate method. Than I have Decorator class decorator, that adds some effect to the method of a decorated class.

It works perfectly on decorated class B, but its effect is not inheritable and hence D does not inherit that effect.

Thats what I want to achieve - learn how to make inheritable class decorators. Or it can be called as metaclass mixins.

How to achieve this effect? All my tries with metaclasses failed with metaclass conflict error.

Gill Bates
  • 14,330
  • 23
  • 70
  • 138

1 Answers1

0

Both classes B and D have their own reference to the method. That's because you assign it in your Meta's __init__ method every time new class is creating. You can check it by viewing __dict__ attribute (like print(D.__dict__)).

To achieve inheritance behavior class D must not contain their own method, in such way it will be taken from parent class B which's method indeed decorated.

From the said above I propose you the following solution:

def decorator(cls):
    def _dec(original_method):
        def _method(*args, **kwargs):
            print('{cls_name} decorated: '.format(cls_name=cls.__name__), end='')
            return original_method(*args, **kwargs)
        return _method
    cls.method = _dec(cls.method)
    return cls


class A():

    def method(self):
        pass


@decorator
class B(A):
    pass


class D(B):
    pass

if __name__ == '__main__':
    A().method() or print('A')
    B().method() or print('B')
    D().method() or print('D')

The result of the execution of this code will be as following:

A
B decorated: B
B decorated: D
renskiy
  • 1,330
  • 1
  • 13
  • 12