Аргументы декоратора класса Python

Я пытаюсь передать необязательные аргументы моему декоратору класса в 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). Я пробовал много решений и читал статьи об этом, но здесь я все еще не могу заставить его работать.

Любая идея решить это? Спасибо!

Ответ 1

@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

Ответ 2

@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 обрабатывать оба варианта использования.

Вместо этого вы можете использовать декоратор factory, который может принимать необязательные аргументы 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.

Ответ 3

Я многое узнал из этого вопроса, спасибо всем. Разве это не ответ, чтобы положить пустые скобки на первый @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.

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