我是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。
答案 0 :(得分:1)
实际上,尽管t
和g
的分配确实在创建新实例,但是您传递的是原始包装函数spam
的相同实例。装饰后,spam
不再是一个函数,而是tracer
的一个实例。这就是Python用来处理类围绕对象的包装的设计方式:对象的名称成为包装对象的实例。
每次创建tracer(spam)
时,func
中的属性tracer
是原始包装函数spam
的实例。因此,在调用包装的值时,在self.func(*args)
中调用tracer.__call__
,从而触发func.__call__
,从而使calls
递增。
tracer
,t
和g
的实例都被传递给tracer
,spam
的相同实例,后者被分配给属性func
。因此,t.func
和g.func
都是实例,因此引用spam
及其所有属性。因此,当您调用t.func
和g.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)
您的问题是t
和g
是双重包装的跟踪器。也就是说,它们是一个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
),并且t
和g
将直接应用于该函数,而无需内在的tracer
挡住了路。