Python functools.wraps эквивалентен для классов

При определении декоратора, использующего класс, как я могу автоматически переносить __name__ , __module__ и __doc__ ? Обычно я использую декоратор @wraps из functools. Вот что я сделал для класса (это не совсем мой код):

 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 __repr__(self): return self.func.__repr__() def __get__(self, obj, objtype): return functools.partial(self.__call__, obj) __doc__ = property(lambda self:self.func.__doc__) __module__ = property(lambda self:self.func.__module__) __name__ = property(lambda self:self.func.__name__) 

Есть ли стандартный декоратор для автоматизации создания модуля имени и документа? Кроме того, для автоматизации метода get (я предполагаю, что это для создания связанных методов?) Есть ли отсутствующие методы?

  • Метод перегрузки декоратора
  • Изменить функцию в декораторе
  • Превосходная практика декоратора Python, используя класс и функцию
  • list @property оформленные методы в классе python
  • декораторы в стандартной библиотеке python (в частности, @deprecated)
  • Как работает этот декоратор Python?
  • Каковы некоторые распространенные применения декораторов Python?
  • Объясните, как работает декоратор Python
  • 5 Solutions collect form web for “Python functools.wraps эквивалентен для классов”

    Кажется, что все пропустили очевидное решение.

     >>> import functools >>> class memoized(object): """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): self.func = func self.cache = {} functools.update_wrapper(self, func) ## TA-DA! ## def __call__(self, *args): pass # Not needed for this demo. >>> @memoized def fibonacci(n): """fibonacci docstring""" pass # Not needed for this demo. >>> fibonacci <__main__.memoized object at 0x0156DE30> >>> fibonacci.__name__ 'fibonacci' >>> fibonacci.__doc__ 'fibonacci docstring' 

    Я не знаю таких вещей в stdlib, но мы можем создать свои собственные, если нам нужно.

    Что-то вроде этого может работать:

     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 

    Применение:

     @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__) 

    Вывод:

     <function fibonacci at 0x14627c0> __doc__: fibonacci docstring __name__: fibonacci 

    РЕДАКТИРОВАТЬ:

    И если вам интересно, почему это не было включено в stdlib, это потому, что вы можете обернуть свой декоратор класса в декораторе функций и использовать functools.wraps следующим образом:

     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) 

    Все, что нам действительно нужно сделать, это изменить поведение декоратора так, чтобы оно было «гигиеничным», то есть оно сохраняло атрибуты.

     #!/usr/bin/python3 def hygienic(decorator): def new_decorator(original): wrapped = decorator(original) wrapped.__name__ = original.__name__ wrapped.__doc__ = original.__doc__ wrapped.__module__ = original.__module__ return wrapped return new_decorator 

    Это ВСЕ, что вам нужно. В целом. Он не сохраняет подпись, но если вы действительно хотите, чтобы вы могли использовать библиотеку для этого. Я также пошел вперед и переписал код memoization, чтобы он работал и с аргументами ключевого слова. Также была ошибка, при которой не удалось преобразовать ее в хешируемый кортеж, чтобы он не работал в 100% случаев.

    Демонстрация перезаписываемого memoized декоратора с @hygienic изменяющим его поведение. memoized теперь является функцией, которая обертывает исходный класс, хотя вы можете (как и другой ответ) написать класс упаковки или, что еще лучше, то, что обнаруживает, является ли это классом, и если так обертывает метод __init__ .

     @hygienic class memoized: def __init__(self, func): self.func = func self.cache = {} def __call__(self, *args, **kw): try: key = (tuple(args), frozenset(kw.items())) if not key in self.cache: self.cache[key] = self.func(*args,**kw) return self.cache[key] except TypeError: # uncacheable -- for instance, passing a list as an argument. # Better to not cache than to blow up entirely. return self.func(*args,**kw) 

    В бою:

     @memoized def f(a, b=5, *args, keyword=10): """Intact docstring!""" print('f was called!') return {'a':a, 'b':b, 'args':args, 'keyword':10} x=f(0) #OUTPUT: f was called! print(x) #OUTPUT: {'a': 0, 'b': 5, 'keyword': 10, 'args': ()} y=f(0) #NO OUTPUT - MEANS MEMOIZATION IS WORKING print(y) #OUTPUT: {'a': 0, 'b': 5, 'keyword': 10, 'args': ()} print(f.__name__) #OUTPUT: 'f' print(f.__doc__) #OUTPUT: 'Intact docstring!' 

    Мне нужно было что-то, что бы обернуть оба класса и функции и написало это:

     def wrap_is_timeout(base): '''Adds `.is_timeout=True` attribute to objects returned by `base()`. When `base` is class, it returns a subclass with same name and adds read-only property. Otherwise, it returns a function that sets `.is_timeout` attribute on result of `base()` call. Wrappers make best effort to be transparent. ''' if inspect.isclass(base): class wrapped(base): is_timeout = property(lambda _: True) for k in functools.WRAPPER_ASSIGNMENTS: v = getattr(base, k, _MISSING) if v is not _MISSING: try: setattr(wrapped, k, v) except AttributeError: pass return wrapped @functools.wraps(base) def fun(*args, **kwargs): ex = base(*args, **kwargs) ex.is_timeout = True return ex return fun 

    Другое решение, использующее наследование:

     import functools import types class CallableClassDecorator: """Base class that extracts attributes and assigns them to self. By default the extracted attributes are: ['__doc__', '__name__', '__module__']. """ def __init__(self, wrapped, assigned=functools.WRAPPER_ASSIGNMENTS): for attr in assigned: setattr(self, attr, getattr(wrapped, attr)) super().__init__() def __get__(self, obj, objtype): return types.MethodType(self.__call__, obj) 

    И, использование:

     class memoized(CallableClassDecorator): """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, function): super().__init__(function) self.function = function self.cache = {} def __call__(self, *args): try: return self.cache[args] except KeyError: value = self.function(*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.function(*args) 
    Python - лучший язык программирования в мире.