如何在可调用装饰器对象中引用对象实例?

2 投票
1 回答
588 浏览
提问于 2025-04-17 13:59

背景:

我想给函数加上一些装饰,这样我就可以追踪它们的统计信息。参考了这篇文章,我开始尝试制作自己的可调用装饰器对象。

这是我最后得到的结果:

def Stats(fn):
    Class StatsObject(object):
        def __init__(self, fn):
            self.fn = fn
            self.stats = {}

        def __call__(self, obj, *args, **kwargs):
            self.stats['times_called'] = self.stats.get('times_called', 0) + 1
            return self.fn(obj, *args, **kwargs)

    function = StatsObject(fn)
    def wrapper(self, *args **kwargs):
        return function(self, *args, **kwargs)
    return wrapper

Class MockClass(object):
    @Stats
    def mock_fn(self, *args, **kwargs):
        # do things

问题:

这个装饰器确实能正确调用mock_fn函数,但我在包装函数外面没有办法访问到统计对象。也就是说,我不能这样做:

mc = MockClass()
mc.mock_fn()
mc.mock_fn.stats
# HasNoAttribute Exception

然后我尝试修改以下代码,因为我意识到这是一个作用域的问题:

从:

    function = StatsObject(fn)
    def wrapper(self, *args **kwargs):
        return function(self, *args, **kwargs)
    return wrapper

到:

    function = StatsObject(fn)
    return function

但当然,我失去了self的引用(self变成了StatsObject的实例,obj变成了第一个参数,而MockClass对象的self引用就丢失了)。

所以我明白第一个问题的原因,但对第二个问题不太明白。有没有办法把MockClass的self引用传递给StatsObject的__call__函数呢?

1 个回答

2

在Python中,函数其实也可以有自己的属性。

def Stats(fn):
    class StatsObject(object):
        def __init__(self, fn):
            self.fn = fn
            self.stats = {}

        def __call__(self, obj, *args, **kwargs):
            self.stats['times_called'] = self.stats.get('times_called', 0) + 1
            return self.fn(obj, *args, **kwargs)

    function = StatsObject(fn)
    def wrapper(self, *args **kwargs):
        return function(self, *args, **kwargs)

    # KEY LINE BELOW: make the StatsObject available outside as "stats_fn"
    wrapper.stats_fn = function

    return wrapper

class MockClass(object):
    @Stats
    def mock_fn(self, *args, **kwargs):
        # do things

这里的关键是把StatsObject这个实例(你可能把它误称为function)作为一个属性,赋值给你从装饰器返回的那个函数。

一旦你这么做了,self.mock_fn.stats_fn.stats(注意,不是self.mock_fn()!这个属性是在函数上,而不是它的返回值上)就可以在MockClass的一个实例中使用,而MockClass.mock_fn.stats_fn.stats则可以在外部访问。这个统计数据在所有MockClass的实例中都是共享的(因为装饰器只调用一次,而不是每个实例调用一次),这可能是你想要的,也可能不是。

撰写回答