Подтвердить что ты не робот

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

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

class memoized:
    """Decorator that caches a function 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 (я предполагаю, что для создания связанных методов?) Есть ли отсутствующие методы?

4b9b3361

Ответ 1

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

>>> import functools
>>> class memoized(object):
    """Decorator that caches a function 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'

Ответ 2

Я не знаю таких вещей в 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 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)

Ответ 3

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

#!/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!'

Ответ 4

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

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

Ответ 5

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

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