Python装饰器处理装饰函数的默认参数

爱德华·伯特

我想为类方法创建一个“缓存”装饰器,该装饰器在内部类属性中注册避免多次计算的方法结果(并且我不想使用在中计算的简单属性__init__,因为我甚至始终都不确定要计算一次)。

第一个想法是创建类似于以下的装饰器“缓存”:

def cache(func):
    name = "_{:s}".format(func.__name__)
    def wrapped(obj):
        if not hasattr(obj, name) or getattr(obj, name) is None:
            print "Computing..."
            setattr(obj, name, func(obj))
        else:
            print "Already computed!"
        return getattr(obj, name)
    return wrapped

class Test:
    @cache
    def hello(self):
        return 1000 ** 5

一切正常:

In [121]: t = Test()

In [122]: hasattr(t, '_hello')
Out[122]: False

In [123]: t.hello()
Computing...
Out[123]: 1000000000000000

In [124]: t.hello()
Already computed!
Out[124]: 1000000000000000

In [125]: hasattr(t, '_hello')
Out[125]: True

现在让我们说我想做同样的事情,但是方法可以用参数(带关键字和/或不带关键字)调用时。当然,现在我们将结果存储在一个字典中,而不是将其存储在不同的属性中(名称将是什么?...),该字典的键由* args和** kwargs组成。让我们用元组来做:

def cache(func):
    name = "_{:s}".format(func.__name__)
    def wrapped(obj, *args, **kwargs):
        if not hasattr(obj, name) or getattr(obj, name) is None:
            setattr(obj, name, {})
        o = getattr(obj, name)
        a = args + tuple(kwargs.items())
        if not a in o:
            print "Computing..."
            o[a] = func(obj, *args, **kwargs)
        else:
            print "Already computed!"
        return o[a]
    return wrapped

class Test:
    @cache
    def hello(self, *args, **kwargs):
        return 1000 * sum(args) * sum(kwargs.values())

In [137]: t = Test()

In [138]: hasattr(t, '_hello')
Out[138]: False

In [139]: t.hello()
Computing...
Out[139]: 0

In [140]: hasattr(t, '_hello')
Out[140]: True

In [141]: t.hello(3)
Computing...
Out[141]: 0

In [142]: t.hello(p=3)
Computing...
Out[142]: 0

In [143]: t.hello(4, y=23)
Computing...
Out[143]: 92000

In [144]: t._hello
Out[144]: {(): 0, (3,): 0, (4, ('y', 23)): 92000, (('p', 3),): 0}

由于该方法items无需考虑字典中的顺序就可以将其转换为元组中的字典,因此,如果没有以相同的顺序调用关键字参数,则它可以完美地工作:

In [146]: t.hello(2, a=23,b=34)
Computing...
Out[146]: 114000

In [147]: t.hello(2, b=34, a=23)
Already computed!
Out[147]: 114000

这是我的问题:如果该方法具有默认参数,那么它将不再起作用:

class Test:
    @cache
    def hello(self, a=5):
        return 1000 * a

现在,它不再起作用:

In [155]: t = Test()

In [156]: t.hello()
Computing...
Out[156]: 5000

In [157]: t.hello(a=5)
Computing...
Out[157]: 5000

In [158]: t.hello(5)
Computing...
Out[158]: 5000

In [159]: t._hello
Out[159]: {(): 5000, (5,): 5000, (('a', 5),): 5000}

结果的计算是3次,因为参数的给出方式不同(即使它们是“相同”的参数!)。

有人知道我如何在装饰器内部捕获赋予该函数的“默认”值吗?

谢谢

瓦迪姆·史卡伯达(Vadim Shkaberda)

取决于参数的函数结构有多复杂,可以有各种解决方案。我更喜欢的解决方案是将内部函数添加到中hello如果您不想更改缓存的名称,请给其外部函数使用相同的名称:

class Test:
    def hello(self, a=5):
        @cache
        def hello(self, a):
            return 1000 * a
        return hello(self, a)

t = Test()
t.hello()
t.hello(a=5)
t.hello(5)
t._hello

Out[111]: Computing...
Already computed!
Already computed!
{(5,): 5000}

另一种方法是在装饰器中添加对默认变量的检查,例如:

def cache(func):
    name = "_{:s}".format(func.__name__)
    def wrapped(obj, *args, **kwargs):
        if not hasattr(obj, name) or getattr(obj, name) is None:
            setattr(obj, name, {})
        o = getattr(obj, name)
        a = args + tuple(kwargs.items())
        if func.func_defaults: # checking if func have default variable
            for k in kwargs.keys():
                if k in func.func_code.co_varnames and kwargs[k] == func.func_defaults[0]:
                    a = ()
            if args:
                if args[0] == func.func_defaults[0]:
                    a = ()
        if not a in o:
            print "Computing..."
            o[a] = func(obj, *args, **kwargs)
        else:
            print "Already computed!"
        return o[a]
    return wrapped

class Test:
    @cache
    def hello(self, a=5):
        return 1000 * a

t = Test()
t.hello()
t.hello(a=5)
t.hello(5)
t._hello

Out[112]: Computing...
Already computed!
Already computed!
{(): 5000}

如果您有例如2个默认变量,则第一个代码(带有内部函数)仍然可以使用,而第二个代码则需要在“默认变量检查规则”中进行更改。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

Python装饰器处理装饰函数的默认参数

来自分类Dev

用装饰器处理函数参数

来自分类Dev

用装饰器处理函数参数

来自分类Dev

带有装饰函数参数的python装饰器

来自分类Dev

具有装饰函数任意定位参数的 Python 装饰器

来自分类Dev

在Sphinx文档中保留包装/装饰的Python函数的默认参数

来自分类Dev

Python装饰器提取参数

来自分类Dev

带参数的 Python 装饰器

来自分类Dev

Python函数装饰器之谜

来自分类Dev

Python装饰器函数执行

来自分类Dev

Python装饰器函数变量

来自分类Dev

Python函数装饰器之谜

来自分类Dev

Python装饰器向函数及其签名添加参数

来自分类Dev

使用包装函数中的变量的python装饰器参数

来自分类Dev

调用装饰函数时将参数传递给装饰器

来自分类Dev

装饰器,允许函数接受任意参数

来自分类Dev

将参数传递给装饰器函数

来自分类Dev

装饰器,用于检查函数的参数

来自分类Dev

装饰器将参数更改为函数

来自分类Dev

在函数内部更改装饰器的参数

来自分类Dev

用python装饰装饰器

来自分类Dev

从装饰器的“推送”属性到Python中的装饰函数

来自分类Dev

带参数的装饰器

来自分类Dev

带参数的装饰器

来自分类Dev

带参数的装饰器

来自分类Dev

带参数的装饰器

来自分类Dev

使用@语法的python装饰器参数

来自分类Dev

如何理解python装饰器参数传递

来自分类Dev

Python装饰器访问参数的名称