Как указать необязательный аргумент для декораторов на основе классов? - PullRequest
4 голосов
/ 16 мая 2011

Как бы я написал декоратор, как это.Я хочу иметь возможность указать значение для max_hits, когда я вызываю декоратор (или опционально оставляю его).

Например, желаемое использование будет

@memoize(max_hits=7)
def a(val):
    print val

или

@memoize
def a(val):
    print val

(Использование первого примера дает ошибку о неверных аргументах.)

Декоратор:

class memoize:
    """A decorator to cache previosly seen function inputs.

    usage:
        @memoize
        def some_func(..
    """
    def __init__(self, function, max_hits=None):
        self.max_hits = max_hits
        self.function = function
        self.memoized = {}

    def __call__(self, *args, **kwargs):
        key = (args,tuple(kwargs.items()))
        try:
            return self.memoized[key]
        except KeyError:
            self.memoized[key] = self.function(*args,**kwargs)
        return self.memoized[key]

Ответы [ 2 ]

8 голосов
/ 16 мая 2011

Вы должны сделать memoize функцию , которая принимает необязательный аргумент max_hits и возвращает декоратор (то есть другой вызываемый объект, который примет функцию в качестве первого аргумента); в этом случае вы можете использовать следующие два синтаксиса:

@memoize()
def func(x):
    [...]

@memoize(max_hits=7)
def func(x):
    [...]

Итак, возможно, что-то вроде:

def memoize(max_hits=None):
    """Returns a decorator to cache previosly seen function inputs.

    usage:
    @memoize()
    def some_func(..
    """
    class decorator:
        def __init__(self, function):
            self.max_hits = max_hits
            self.function = function
            self.memoized = {}

        def __call__(self, *args, **kwargs):
            key = (args,tuple(kwargs.items()))
            try:
                return self.memoized[key]
            except KeyError:
                self.memoized[key] = self.function(*args,**kwargs)
            return self.memoized[key]

    return decorator

Обратите внимание, что @memoize() будет работать, но ваш изначально желаемый синтаксис @memoize не будет; в последнем случае функция, которую вы украшаете с помощью @memoize, будет передана memoize в качестве первого аргумента (max_hits). Если вы хотите обработать этот случай, вы можете расширить memoize следующим образом:

def memoize(max_hits=None):
    if callable(max_hits):
        # For sake of readability...
        func = max_hits
        decorator = memoize(max_hits=None)
        return decorator(func)
    [...original implementation follows here...]
4 голосов
/ 16 мая 2011

Если вы используете 3.2+, не пишите это самостоятельно, используйте from functools import lru_cache вместо

Если вы хотите поддерживать версию без круглых скобок, также лучше использовать значение Sentinel для аргумента функции, а не анализировать «неправильный» аргумент. То есть:

class Memoized(object):
    # As per the memoize definition in the question

# Leave out the "*, " in 2.x, since keyword-only arguments are only in 3.x
from functools import partial
_sentinel = object()
def memoize(_f=_sentinel, *, max_hits=None):
    if _f is _sentinel:
        return partial(Memoized, max_hits=max_hits)
    return Memoized(_f, max_hits=max_hits)
...