简体   繁体   中英

How to make arguments in python decorator configurable?

I am using cachetools for some basic caching. Here is an example of how I am using it:

class Access:

  @cached(cache=TTLCache(maxsize=5, ttl=10))
  def get_some_value(input: str):
    # do some calls and return a value

The problem here is how do I make the maxsize and ttl configurable? I cannot do something like

class Access:
  def __init__(self, maxsize: int = 5, ttl: int = 10):
    self.maxsize = maxsize
    self.ttl = ttl

  @cached(cache=TTLCache(maxsize=self.maxsize, ttl=self.ttl))
  def get_some_value(input: str):
    # do some calls and return a value

I am looking for a way to inject those values if required and also have a default. Any helpful pointers? Also, get_some_value() need not be an instance method. I could just make it a class method or module level also if need be.

To deal with this, we can use the fact that Python decorators are simply functions that return other functions.

Suppose you have this eggs decorator:

def eggs(foo=10, bar=20):
    def wrapper_gen(func):
        def wrapper(*args):
            print(foo, bar)
            func(*args)
        return wrapper
    return wrapper_gen

And this Spam class:

class Spam:
    @eggs(foo=10, bar=20)
    def baz(self, input):
        print(input)

We can call the baz method as such:

Spam().baz("Hello, world!")

And this gives us

10 20
Hello, world!

Now, instead of directly decorating the function, we'll decorate in our __init__ method:

class Spam:
    def __init__(self, foo=10, bar=20):
        self.baz = eggs(foo=foo, bar=bar)(self._baz_func)

    def _baz_func(self, input):
        print(input)

And now:

Spam(foo=20, bar=30).baz("Hello, world!")

This outputs

20 30
Hello, world!

The reason this works is that this:

@foo
def bar():
    ...

is shorthand for this:

def bar():
    ...

bar = foo(bar)

This can't work using the regular decorator syntax, for two reasons: get_some_value belongs to the class, so it cannot have different behaviour for different instances since there's only one "copy" of it; and the decorator is executed at the time of the class declaration, not at the time of the instance creation, so there are no __init__ arguments yet.

However, you can get the result you want by applying the decorator explicitly in the __init__ method:

class Access:
    def __init__(self, maxsize: int = 5, ttl: int = 10):
        decorator = cached(cache=TTLCache(maxsize=maxsize, ttl=ttl))
        self.get_some_value = decorator(self.get_some_value)

    def get_some_value(self, input: str):
        ...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM