Question

I was trying to have my class-based decorator keeping the repr() behavior of the original wrapped function (to match the way the functools.wraps decorator works on functions). I am using python 3.3.

First I tried functools:

import functools

class ClassBasedDecorator():
    def __init__(self, fn):
        self.fn = fn
        functools.update_wrapper(self, fn)
    def __call__(self, *args, **kwargs):
        self.fn(*args, **kwargs)

@ClassBasedDecorator
def wrapped(text):
    pass

But when I call repr() on the decorated function, I get:

>>> repr(wrapped)
'<__main__.ClassBasedDecorator object at 0x2d8860b6850>'

Very well, so I tried to customize the __repr__ method of my decorator, which is supposed to be called by repr().

Using functools again:

class ClassBasedDecorator():
    def __init__(self, fn):
        self.fn = fn
        functools.update_wrapper(
            self, fn,
            assigned=functools.WRAPPER_ASSIGNMENTS + ('__repr__',)
        )
    def __call__(self, *args, **kwargs):
        self.fn(*args, **kwargs)

Doesn't change the output, but something interesting happens:

>>> repr(wrapped)
'<__main__.ClassBasedDecorator object at 0x2d8860b69d0>'
>>> wrapped.__repr__()
'<function wrapped at 0x2d8860a9710>'

Explicitly setting the __repr__ method of the decorator instance has the same effect.

After a little more tests I deduced repr(instance) actually calls instance.__class__.__repr__(instance). Thus the overriden __repr__ method of the instance is never called.


So here are my questions:

  • Why does repr(instance) call the instance.__class__.__repr__(instance) instead of instance.__repr__()? Or have I missed something else?
  • How would you fully reproduce what functools.wraps does with function-based decorators to class-based decorators (including altering the result of repr() calls on the decorated function)?
Was it helpful?

Solution

Special methods are always looked up on the type of the instance (here the class object), not on the instance. Otherwise a __repr__ on a class would be used when you tried to print the representation of the class itself; type(class).__repr__(class) would use the correct magic method, while class.__repr__() would raise an exception because self was not provided.

Implement your own __repr__ hooks:

class ClassBasedDecorator():
    def __init__(self, fn):
        self.fn = fn
        functools.update_wrapper(self, fn)
    def __call__(self, *args, **kwargs):
        self.fn(*args, **kwargs)
    def __repr__(self):
        return repr(self.fn)

e.g. still copy over the __module__, __name__ and __doc__ attributes, and copy over the attributes from the function __dict__, but make any special methods a proxy.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top