首页 > 解决方案 > 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.

标签: pythonpython-3.x

解决方案


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)

推荐阅读