Python class decorator arguments
Python class decorator arguments
@Cache(max_hits=100, timeout=50)
calls __init__(max_hits=100, timeout=50)
, so you arent satisfying the function
argument.
You could implement your decorator via a wrapper method that detected whether a function was present. If it finds a function, it can return the Cache object. Otherwise, it can return a wrapper function that will be used as the decorator.
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
@Cache
def double(...):
...
is equivalent to
def double(...):
...
double=Cache(double)
While
@Cache(max_hits=100, timeout=50)
def double(...):
...
is equivalent to
def double(...):
...
double = Cache(max_hits=100, timeout=50)(double)
Cache(max_hits=100, timeout=50)(double)
has very different semantics than Cache(double)
.
Its unwise to try to make Cache
handle both use cases.
You could instead use a decorator factory that can take optional max_hits
and timeout
arguments, and returns a decorator:
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. If the class Cache
has no other methods besides __init__
and __call__
, you can probably move all the code inside the _cache
function and eliminate Cache
altogether.
Python class decorator arguments
Ive learned a lot from this question, thanks all. Isnt the answer just to put empty brackets on the first @Cache
? Then you can move the function
parameter to __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
Although I think this approach is simpler and more concise:
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
If you forget the parentheses when using the decorator, unfortunately you still dont get an error until runtime, as the outer decorator parameters are passed the function youre trying to decorate. Then at runtime the inner decorator complains:
TypeError: caching_decorator() takes exactly 1 argument (0 given).
However you can catch this, if you know your decorators parameters are never going to be a callable:
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
If you now try:
@cache
def some_method()
pass
You get an AssertionError
on declaration.
On a total tangent, I came across this post looking for decorators that decorate classes, rather than classes that decorate. In case anyone else does too, this question is useful.