Аргументы декоратора класса Python - PullRequest
11 голосов
/ 21 сентября 2011

Я пытаюсь передать необязательные аргументы моему декоратору класса в python. Ниже код у меня в настоящее время:

class Cache(object):
    def __init__(self, function, max_hits=10, timeout=5):
        self.function = function
        self.max_hits = max_hits
        self.timeout = timeout
        self.cache = {}

    def __call__(self, *args):
        # Here the code returning the correct thing.


@Cache
def double(x):
    return x * 2

@Cache(max_hits=100, timeout=50)
def double(x):
    return x * 2

Второй декоратор с аргументами для перезаписи по умолчанию (max_hits=10, timeout=5 в моей __init__ функции) не работает, и я получил исключение TypeError: __init__() takes at least 2 arguments (3 given). Я попробовал много решений и прочитал статьи об этом, но здесь я все еще не могу заставить его работать.

Есть идеи, чтобы решить эту проблему? Спасибо!

Ответы [ 3 ]

14 голосов
/ 21 сентября 2011

@Cache(max_hits=100, timeout=50) вызывает __init__(max_hits=100, timeout=50), поэтому вы не удовлетворяете аргументу function.

Вы можете реализовать свой декоратор с помощью метода-обертки, который определяет наличие функции. Если он находит функцию, он может вернуть объект Cache. В противном случае он может вернуть функцию-оболочку, которая будет использоваться в качестве декоратора.

class _Cache(object):
    def __init__(self, function, max_hits=10, timeout=5):
        self.function = function
        self.max_hits = max_hits
        self.timeout = timeout
        self.cache = {}

    def __call__(self, *args):
        # Here the code returning the correct thing.

# wrap _Cache to allow for deferred calling
def Cache(function=None, max_hits=10, timeout=5):
    if function:
        return _Cache(function)
    else:
        def wrapper(function):
            return _Cache(function, max_hits, timeout)

        return wrapper

@Cache
def double(x):
    return x * 2

@Cache(max_hits=100, timeout=50)
def double(x):
    return x * 2
13 голосов
/ 21 сентября 2011
@Cache
def double(...): 
   ...

эквивалентно

def double(...):
   ...
double=Cache(double)

В то время как

@Cache(max_hits=100, timeout=50)
def double(...):
   ...

эквивалентно

def double(...):
    ...
double = Cache(max_hits=100, timeout=50)(double)

Cache(max_hits=100, timeout=50)(double) имеет очень отличную семантику от Cache(double).

Неразумно пытаться заставить Cache обрабатывать оба варианта использования.

Вместо этого можно использовать фабрику декораторов, которая может принимать необязательные аргументы max_hits и timeout, и возвращаетдекоратор:

class Cache(object):
    def __init__(self, function, max_hits=10, timeout=5):
        self.function = function
        self.max_hits = max_hits
        self.timeout = timeout
        self.cache = {}

    def __call__(self, *args):
        # Here the code returning the correct thing.

def cache_hits(max_hits=10, timeout=5):
    def _cache(function):
        return Cache(function,max_hits,timeout)
    return _cache

@cache_hits()
def double(x):
    return x * 2

@cache_hits(max_hits=100, timeout=50)
def double(x):
    return x * 2

PS.Если у класса Cache нет других методов, кроме __init__ и __call__, вы, вероятно, можете переместить весь код в функцию _cache и полностью исключить Cache.

2 голосов
/ 14 июня 2017

Я многому научился из этого вопроса, спасибо всем. Разве ответ не состоит в том, чтобы поставить пустые скобки в первый @Cache? Затем вы можете переместить параметр function в __call__.

class Cache(object):
    def __init__(self, max_hits=10, timeout=5):
        self.max_hits = max_hits
        self.timeout = timeout
        self.cache = {}

    def __call__(self, function, *args):
        # Here the code returning the correct thing.

@Cache()
def double(x):
    return x * 2

@Cache(max_hits=100, timeout=50)
def double(x):
    return x * 2

Хотя я думаю, что этот подход проще и лаконичнее:

def cache(max_hits=10, timeout=5):
    def caching_decorator(fn):
        def decorated_fn(*args ,**kwargs):
            # Here the code returning the correct thing.
        return decorated_fn
    return decorator

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

TypeError: caching_decorator () принимает ровно 1 аргумент (задано 0).

Однако вы можете это уловить, если знаете, что параметры вашего декоратора никогда не будут вызываться:

def cache(max_hits=10, timeout=5):
    assert not callable(max_hits), "@cache passed a callable - did you forget to parenthesize?"
    def caching_decorator(fn):
        def decorated_fn(*args ,**kwargs):
            # Here the code returning the correct thing.
        return decorated_fn
    return decorator

Если вы сейчас попробуете:

@cache
def some_method()
    pass

Вы получаете AssertionError по объявлению.

По касательной я наткнулся на этот пост в поисках декораторов, которые украшают классы, а не классы, которые украшают. В случае, если кто-то еще делает, этот вопрос будет полезен.

...