在Python中基于类的装饰器中存储信息

时间:2018-07-15 19:43:15

标签: python python-3.x decorator python-decorators

我是Python的初学者,并且从Lutz的书中学习Decorators。我在下面遇到了这段代码。我不确定tracer为何以及如何保留函数调用的数量,即使创建了新实例也是如此。

class tracer:
    def __init__(self,func):
        self.calls=0
        self.func=func
    def __call__(self, *args):
        self.calls+=1
        print('call %s to %s' %(self.calls,self.func.__name__))
        self.func(*args)

@tracer
def spam(a,b,c):
    print (a+b+c)

spam(1,2,3) #Here calls counter increments to 1
t= tracer(spam)
t.func(3,4,5) # here calls counter increments to 2 even though I called `spam` using new instance `t`

g=tracer(spam)
g.func(4,5,6)  #calls counter increments to 3.

如上所述,即使创建了新实例,calls计数器状态也会保留。

有人可以解释为什么会这样吗?我尝试使用PyCharm调试代码,似乎spam的内存位置保持不变,而不管特定实例的调用如何。


我正在使用Anaconda Distribution中的Python 3.6。

2 个答案:

答案 0 :(得分:1)

实际上,尽管tg的分配确实在创建新实例,但是您传递的是原始包装函数spam的相同实例。装饰后,spam不再是一个函数,而是tracer的一个实例。这就是Python用来处理类围绕对象的包装的设计方式:对象的名称成为包装对象的实例。


每次创建tracer(spam)时,func中的属性tracer是原始包装函数spam的实例。因此,在调用包装的值时,在self.func(*args)中调用tracer.__call__,从而触发func.__call__,从而使calls递增。

tracertg的实例都被传递给tracerspam的相同实例,后者被分配给属性func。因此,t.funcg.func都是实例,因此引用spam及其所有属性。因此,当您调用t.funcg.func时,您将触发spam.__call__,从而在calls中增加spam

class tracer:
   def __init__(self, _func):
     self.func = _func
     self.calls = 0
   def __repr__(self):
     return f"{self.__class__.__name__}(storing {self.func.__name__})"
   def __call__(self, *args):
      self.calls += 1
      print(f"__call__ executed in {repr(self)}")
      return self.func(*args)

@tracer
def spam(a,b,c):
  print (a+b+c)

>>>spam(1, 2, 3)
__call__ executed in tracer(storing spam)
t= tracer(spam)
>>>t.func(1, 2, 3)
__call__ executed in tracer(storing spam)
g=tracer(spam)
>>>g.func(1, 2, 3)
__call__ executed in tracer(storing spam)

答案 1 :(得分:1)

您的问题是tg是双重包装的跟踪器。也就是说,它们是一个tracer实例,其中包含另一个tracer实例(最终指的是函数)。外部tracer不能真正正常工作,因为内部tracer没有__name__属性,因为它不是函数。您只能调用t.func,它绕过外部跟踪器(及其计数)以直接调用内部跟踪器。

您可以通过为每个__name__添加一个tracer属性来使代码工作:

class tracer:
    def __init__(self,func):
        self.calls=0
        self.func=func
        self.__name__ = 'tracer wrapping %r' % func.__name__   # give ourselves a name
    def __call__(self, *args):
        self.calls+=1
        print('call %s to %s' %(self.calls,self.func.__name__))
        self.func(*args)    # note, you should probably return the result of this call

现在您可以调用t(3, 4, 5)g(5, 6, 7),每个调用将打印出两个计数,一个用于内部跟踪器,一个用于外部跟踪器。外部计数将是单独的(每个都从1开始),但是内部计数将被共享(就像您最初看到的一样)。

当然,可能是您不希望嵌套的跟踪器。在这种情况下,您可能希望从函数之前删除@tracer行。这就是应用内​​部跟踪器的地方,等效于将spam = tracer(spam)放在函数spam的定义之后。如果没有这一行,spam将直接直接引用该函数(不包含tracer),并且tg将直接应用于该函数,而无需内在的tracer挡住了路。