2

语境:

我希望能够装饰函数,以便我可以跟踪它们的统计信息。使用这篇文章作为参考,我开始尝试制作我自己的可调用装饰器对象。

这是我最终得到的结果:

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 函数,但我没有对包装函数之外的 stats 对象的引用。即我不能这样做:

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 成为 StatsObject 实例,obj 成为第一个 arg,MockClass 对象自引用丢失)。

所以我明白为什么会发生第一个问题,但不是第二个。有什么方法可以将 MockClass 的自引用传递给 StatsObject__call__函数?

4

1 回答 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实例(您可能会误导地,本地命名)分配为您从 decorator 返回function的函数的属性。

一旦你这样做,self.mock_fn.stats_fn.stats(不是self.mock_fn()!属性在函数上,而不是它的返回值)将在 的实例中工作MockClass,并且MockClass.mock_fn.stats_fn.stats在外部可用。统计信息将在所有实例中是全局的MockClass(因为装饰器被调用一次,而不是每个实例一次),这可能是也可能不是您想要的。

于 2013-01-28T23:32:46.447 回答