Python中基于类的decorator中存储的信息

2024-05-23 19:22:35 发布

您现在位置:Python中文网/ 问答频道 /正文

我是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发行版的python3.6


Tags: to实例代码selfdefcounterargsspam
2条回答

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

通过向每个tracer添加__name__属性,可以使代码正常工作:

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妨碍

实际上,当tg的赋值确实在创建新实例时,您传递的是原始包装函数的相同实例spam。经过修饰后,spam不再是函数,而是tracer的实例。这就是Python如何设计来处理围绕对象的类包装:对象的名称成为包装对象的实例


每当创建tracer(spam)时,tracer中的属性func就是原始包装函数spam的实例。因此,在调用包装值时,self.func(*args)tracer.__call__中被调用,触发func.__call__,它增加calls

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

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)

相关问题 更多 >