Python functools.wraps等同于类

Nei*_*l G 69 python decorator

使用类定义装饰器时,如何自动转移__name__,__module____doc__?通常,我会使用functools的@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 __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__)
Run Code Online (Sandbox Code Playgroud)

是否有标准的装饰器来自动创建名称模块和文档?另外,要自动化get方法(我假设用于创建绑定方法?)是否有任何缺少的方法?

sam*_*yse 46

似乎每个人都错过了明显的解决方案.

>>> 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'
Run Code Online (Sandbox Code Playgroud)

  • `__name__`和`__doc__`是在_instance_上设置的,而不是类,这是`help(instance)`总是使用的.要修复,不能使用基于类的装饰器实现,而是必须将装饰器实现为函数.有关详细信息,请参阅http://stackoverflow.com/a/25973438/1988505. (13认同)
  • 我不知道为什么我的答案昨天突然被降价了。没有人询问如何让 help() 工作。在 3.5 中,inspect.signature() 和 inspect.from_callable() 有了一个新的 follow_wrapped 选项;也许 help() 应该做同样的事情? (2认同)

mou*_*uad 23

我在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
Run Code Online (Sandbox Code Playgroud)

用法:

@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__)
Run Code Online (Sandbox Code Playgroud)

输出:

<function fibonacci at 0x14627c0>
__doc__:  fibonacci docstring
__name__:  fibonacci
Run Code Online (Sandbox Code Playgroud)

编辑:

如果你想知道为什么这不包含在stdlib中是因为你可以将你的类装饰器包装在一个函数decorater中并使用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)
Run Code Online (Sandbox Code Playgroud)


Ant*_*ile 21

事实证明,有一个简单的解决方案,可以使用functools.wraps它自己:

import functools

def dec(cls):
    @functools.wraps(cls, updated=())
    class D(cls):
        decorated = 1
    return D


@dec
class C:
    """doc"""

print(f'{C.__name__=} {C.__doc__=} {C.__wrapped__=}')
Run Code Online (Sandbox Code Playgroud)
$ python3 t.py 
C.__name__='C' C.__doc__='doc' C.__wrapped__=<class '__main__.C'>
Run Code Online (Sandbox Code Playgroud)

请注意,updated=()需要防止尝试更新类__dict__(此输出不包含updated=()):

$ python t.py
Traceback (most recent call last):
  File "t.py", line 26, in <module>
    class C:
  File "t.py", line 20, in dec
    class D(cls):
  File "/usr/lib/python3.8/functools.py", line 57, in update_wrapper
    getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
AttributeError: 'mappingproxy' object has no attribute 'update'
Run Code Online (Sandbox Code Playgroud)

  • @NeilG,如果它让你感觉更好,这至少回答了*我的*问题,它映射到OP问题的标题,但不是特定的记忆示例。 (7认同)
  • 这解决了我搜索类的“functools.wraps”等价物的问题 (6认同)

tem*_*oto 5

我需要一些可以包装类和函数的东西,并写道:

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
Run Code Online (Sandbox Code Playgroud)