Python functools.wraps equivalent for classes

前端 未结 5 1684
予麋鹿
予麋鹿 2020-12-04 09:13

When defining a decorator using a class, how do I automatically transfer over__name__, __module__ and __doc__? Normally, I would use

5条回答
  •  再見小時候
    2020-12-04 09:46

    I'm not aware of such things in stdlib, but we can create our own if we need to.

    Something like this can work :

    from functools import WRAPPER_ASSIGNMENTS
    
    
    def class_wraps(cls):
        """Update a wrapper class `cls` to look like the wrapped."""
    
        class Wrapper(cls):
            """New wrapper that will extend the wrapper `cls` to make it look like `wrapped`.
    
            wrapped: Original function or class that is beign decorated.
            assigned: A list of attribute to assign to the the wrapper, by default they are:
                 ['__doc__', '__name__', '__module__', '__annotations__'].
    
            """
    
            def __init__(self, wrapped, assigned=WRAPPER_ASSIGNMENTS):
                self.__wrapped = wrapped
                for attr in assigned:
                    setattr(self, attr, getattr(wrapped, attr))
    
                super().__init__(wrapped)
    
            def __repr__(self):
                return repr(self.__wrapped)
    
        return Wrapper
    

    Usage:

    @class_wraps
    class memoized:
        """Decorator that caches a function's return value each time it is called.
        If called later with the same arguments, the cached value is returned, and
        not re-evaluated.
        """
    
        def __init__(self, func):
            super().__init__()
            self.func = func
            self.cache = {}
    
        def __call__(self, *args):
            try:
                return self.cache[args]
            except KeyError:
                value = self.func(*args)
                self.cache[args] = value
                return value
            except TypeError:
                # uncacheable -- for instance, passing a list as an argument.
                # Better to not cache than to blow up entirely.
                return self.func(*args)
    
        def __get__(self, obj, objtype):
            return functools.partial(self.__call__, obj)
    
    
    @memoized
    def fibonacci(n):
        """fibonacci docstring"""
        if n in (0, 1):
           return n
        return fibonacci(n-1) + fibonacci(n-2)
    
    
    print(fibonacci)
    print("__doc__: ", fibonacci.__doc__)
    print("__name__: ", fibonacci.__name__)
    

    Output:

    
    __doc__:  fibonacci docstring
    __name__:  fibonacci
    

    EDIT:

    And if you are wondering why this wasn't included in the stdlib is because you can wrap your class decorator in a function decorator and use functools.wraps like this:

    def wrapper(f):
    
        memoize = memoized(f)
    
        @functools.wraps(f)
        def helper(*args, **kws):
            return memoize(*args, **kws)
    
        return helper
    
    
    @wrapper
    def fibonacci(n):
        """fibonacci docstring"""
        if n <= 1:
           return n
        return fibonacci(n-1) + fibonacci(n-2)
    

提交回复
热议问题