Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python class decorator arguments

Tags:

I'm trying to pass optional arguments to my class decorator in python. Below the code I currently have:

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 

The second decorator with arguments to overwrite the default one (max_hits=10, timeout=5 in my __init__ function), is not working and I got the exception TypeError: __init__() takes at least 2 arguments (3 given). I tried many solutions and read articles about it, but here I still can't make it work.

Any idea to resolve this? Thanks!

like image 788
Dachmt Avatar asked Sep 20 '11 21:09

Dachmt


2 Answers

@Cache(max_hits=100, timeout=50) calls __init__(max_hits=100, timeout=50), so you aren't 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 
like image 67
lunixbochs Avatar answered Oct 01 '22 08:10

lunixbochs


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

It's 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.

like image 43
unutbu Avatar answered Oct 01 '22 07:10

unutbu