Skip to content Skip to sidebar Skip to footer

Using A Class To Operate Both As Decorator And Decorator Factory

Consider the following decorator function, which either returns a decorated function, or a parametrized decorator function: from functools import wraps, partial, update_wrapper fro

Solution 1:

As was suggested in the comments, you can do this using the __new__ method:

classWrapit:
    def__new__(cls, func=None, *, verb='calling'):
        if func isNone:
            return partial(cls,verb=verb)
        self = super().__new__(cls)
        self.func, self.verb = func, verb
        update_wrapper(self, func)
        return self

    def__call__(self, *args, **kwargs):
        print(f'{self.verb}{self.func.__name__} with {args} and {kwargs}')
        return self.func(*args, **kwargs)

The __new__ method is called whenever you try to instantiate a class, and the return value of that method is used as the result of the attempted instantiation -- even if it's not an instance of the class!

Solution 2:

I accepted @pppery's answer because... it was the answer. I wanted to extend the answer here by showing how one can get a bit more reuse by coding the logic in a parent class. This requires one to separate @pppery's logic into the __new__ and __init__ methods.

from functools import update_wrapper, partial

classDecorator:
    def__new__(cls, func=None, **kwargs):
        if func isNone:
            self = partial(cls, **kwargs)
        else:
            self = super().__new__(cls)
        return update_wrapper(self, func)

    def__init__(self, func=None, **kwargs):
        self.func = func
        for attr_name, attr_val in kwargs.items():
            setattr(self, attr_name, attr_val)

    def__call__(self, *args, **kwargs):
        return self.func(*args, **kwargs)
classWrapit(Decorator):
    def__new__(cls, func=None, *, verb='calling'):
        returnsuper().__new__(cls, func, verb=verb)

    def__call__(self, *args, **kwargs):
        print(f'{self.verb}{self.func.__name__} with {args} and {kwargs}')
        returnsuper().__call__(*args, **kwargs)

classAnotherOne(Decorator):
    def__new__(cls, func=None, *, postproc=lambda x: x):
        returnsuper().__new__(cls, func, postproc=postproc)

    def__call__(self, *args, **kwargs):
        return self.postproc(super().__call__(*args, **kwargs))

Demo:

>>> f = lambda x, y=1: x + y
>>> >>> ff = Wrapit(f, verb='launching')
>>> assert ff(10) == 11
launching <lambda> with (10,) and {}
>>> assert signature(ff)  == signature(f) 
>>> >>> fff = AnotherOne(postproc=str)(f)  # doing it the decorator factory way>>> assert fff(10) == str(11)
>>> assert signature(fff)  == signature(f)

Post a Comment for "Using A Class To Operate Both As Decorator And Decorator Factory"