Python - 如何为基于类的装饰器指定可选参数?

4 投票
2 回答
4315 浏览
提问于 2025-04-16 17:44

我想知道怎么写一个这样的装饰器。我希望在调用装饰器的时候,可以指定最大点击次数(max_hits)的值,或者可以选择不指定。

比如,我想要的用法是:

@memoize(max_hits=7)
def a(val):
    print val

或者

@memoize
def a(val):
    print val

(使用第一个例子会出现关于参数不正确的错误。)

装饰器:

class memoize:
    """A decorator to cache previosly seen function inputs.

    usage:
        @memoize
        def some_func(..
    """
    def __init__(self, function, max_hits=None):
        self.max_hits = max_hits
        self.function = function
        self.memoized = {}

    def __call__(self, *args, **kwargs):
        key = (args,tuple(kwargs.items()))
        try:
            return self.memoized[key]
        except KeyError:
            self.memoized[key] = self.function(*args,**kwargs)
        return self.memoized[key]

2 个回答

4

如果你使用的是3.2版本或更高的版本,别自己写,直接用 from functools import lru_cache 这个就行了。

如果你想支持不带括号的写法,最好使用一个特殊的值作为函数的参数,而不是去检查“错误”的参数。也就是说:

class Memoized(object):
    # As per the memoize definition in the question

# Leave out the "*, " in 2.x, since keyword-only arguments are only in 3.x
from functools import partial
_sentinel = object()
def memoize(_f=_sentinel, *, max_hits=None):
    if _f is _sentinel:
        return partial(Memoized, max_hits=max_hits)
    return Memoized(_f, max_hits=max_hits)
8

你需要创建一个叫做 memoize函数,它可以接受一个可选的参数 max_hits,并返回一个装饰器(也就是另一个可以调用的对象,它会把你要装饰的函数作为第一个参数传入)。在这种情况下,你可以使用以下两种语法:

@memoize()
def func(x):
    [...]

@memoize(max_hits=7)
def func(x):
    [...]

所以,大概可以这样写:

def memoize(max_hits=None):
    """Returns a decorator to cache previosly seen function inputs.

    usage:
    @memoize()
    def some_func(..
    """
    class decorator:
        def __init__(self, function):
            self.max_hits = max_hits
            self.function = function
            self.memoized = {}

        def __call__(self, *args, **kwargs):
            key = (args,tuple(kwargs.items()))
            try:
                return self.memoized[key]
            except KeyError:
                self.memoized[key] = self.function(*args,**kwargs)
            return self.memoized[key]

    return decorator

注意,使用 @memoize() 是可以的,但你想要的 @memoize 语法是行不通的;在后者的情况下,你用 @memoize 装饰的函数会作为第一个参数传给 memoize(也就是 max_hits)。如果你想处理这种情况,可以这样扩展 memoize

def memoize(max_hits=None):
    if callable(max_hits):
        # For sake of readability...
        func = max_hits
        decorator = memoize(max_hits=None)
        return decorator(func)
    [...original implementation follows here...]

撰写回答