Not a class decorator, but a decorator class.
I can write a decorator function and it works fine, but when I try the same implemented as a class (to wrestle out of the nesting, mainly), I get caught in the selfies. Here's a minimal working example:
Note The decorator is used with parentheses like so @DecoratorClass() and not @DecoratorClass. This makes a big difference.
def decorator_function():
    def decorator(fcn):
        def wrapper(self, *args):
            print('decorator_function wrapper self:', self)
            print('decorator_function wrapper args:', args)
            return fcn(self, *args)
        return wrapper
    return decorator
class DecoratorClass:
    def __init__(self):
        self.fcn = None
    def __call__(self, fcn):
        self.fcn = fcn
        return self.wrapper
    def wrapper(self, *args):
        print('DecoratorClass wrapper self:', self)
        print('DecoratorClass wrapper args:', args)
        return self.fcn(*args)
class A:
    @decorator_function()
    def x(self, *args):
        print('A x self:', self)
        print('A x args:', args)
        return 42
    @DecoratorClass()
    def y(self, *args):
        print('A y self:', self)
        print('A y args:', args)
        return 43
a = A()
a.x(1, 2)
a.y(1, 2)
And here is the output.
decorator_function wrapper self: <mytests.debugme.A object at 0x7fa540d315f8>
decorator_function wrapper args: (1, 2)
A x self: <mytests.debugme.A object at 0x7fa540d315f8>
A x args: (1, 2)
DecoratorClass wrapper self: <mytests.debugme.DecoratorClass object at 0x7fa540d31208>
DecoratorClass wrapper args: (1, 2)
A y self: 1
A y args: (2,)
As you can see, the class based approach loses the reference to a. To clarify, I wish the result of a.y(1,2) to be identical to a.x(1,2).
update for @Martijn Pieters
class DecoratorClass:
    def __init__(self):
        self.fcn = None
    def __call__(self, fcn):
        self.fcn = fcn
        return self.wrapper
    def __get__(self, obj, objtype):
        """Support instance methods."""
        import functools
        return functools.partial(self.__call__, obj)
    def wrapper(self, *args):
        print('DecoratorClass wrapper self:', self)
        print('DecoratorClass wrapper args:', args)
        return self.fcn(*args)
a = A()
a.y(1, 2)
Output:
DecoratorClass wrapper self: <mytests.debugme.DecoratorClass object at 0x7fa540d31048>
DecoratorClass wrapper args: (1, 2)
A y self: 1
A y args: (2,)
IOW: the marked duplicate question has no working answer.
