Функции Python.обертывания эквивалентны для классов
при определении декоратора с помощью класса, как я могу автоматически передавать через__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__)
есть ли стандартный декоратор для автоматизации создания модуля имени и doc? Кроме того, чтобы автоматизировать метод get (я предполагаю, что это для создания связанных методов?) Есть ли какие-либо недостающие методы?
5 ответов:
все, кажется, пропустили очевидное решение.
>>> 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
EDIT:
и если вам интересно, почему это не было включено в 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)