首页 > 解决方案 > Python中基于类的装饰器中存储的信息

问题描述

我是 Python 的初学者,从 Lutz 的书中学习装饰器。我在下面遇到了这段代码。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。

标签: pythonpython-3.xdecoratorpython-decorators

解决方案


实际上,虽然 和 的赋值t确实g在创建新实例,但您传递的是原始包装函数的相同实例,spam. 修饰后,spam不再是函数,而是tracer. 这就是 Python 设计处理对象周围类包装的方式:对象的名称成为包装对象的实例。


Anytimetracer(spam)被创建,属性funcintracer是原始包装函数的一个实例spam。因此,当调用包装的值时,self.func(*args)会调用 in tracer.__call__,触发func.__call__,它会递增calls

tracer,t和,的实例g都被传递给 , 的同一个实例tracerspam该实例被分配给 属性func。因此,t.funcg.func都是实例,因此是引用spam,以及它的所有属性。因此,当您调用t.funcand时g.func,您正在触发,因此在 中spam.__call__递增:callsspam

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)

推荐阅读