Python functools.wraps equivalent for classes
在定义使用类的装饰器时,如何自动转移over
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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方法(我假设这是为了创建绑定方法?)有没有遗漏的方法?
似乎每个人都错过了显而易见的解决方案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | >>> 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中有这样的东西,但是如果需要的话,我们可以创建自己的。
像这样的东西可以工作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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 |
用途:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | @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__) |
号
输出:
1 2 3 | <function fibonacci at 0x14627c0> __doc__: fibonacci docstring __name__: fibonacci |
编辑:
如果你想知道为什么stdlib中不包括这个是因为你可以将类decorator包装在函数decorator中,并使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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) |
。
我需要同时包装类和函数并编写以下内容的东西:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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 |
。
我们真正需要做的是修改装饰器的行为,使其"卫生",即它是属性保留。
1 2 3 4 5 6 7 8 9 10 | #!/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代码,这样它也可以处理关键字参数。还有一个bug,如果不能将其转换为哈希元组,将使它在100%的情况下无法工作。
用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @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) |
行动中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | @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!' |
。
使用继承的另一个解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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) |
用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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) |
。