Python 中基于 class 的装饰器中存储的信息
Stored information in class-based decorator in 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。
实际上,虽然 t
和 g
的赋值确实在创建新实例,但您传递的是原始包装函数 spam
的相同实例。修饰后,spam
不再是一个函数,而是tracer
的一个实例。这就是 Python 设计来处理对象周围 class 的包装的方式:对象的名称成为包装对象的实例。
任何时候创建 tracer(spam)
,tracer
中的属性 func
都是原始包装函数 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__
,从而在 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)
您的问题是 t
和 g
是双重包裹的示踪剂。也就是说,它们是一个 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
包裹它),并且 t
和 g
将直接应用于该函数,而不内心的 tracer
妨碍了。
我是 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。
实际上,虽然 t
和 g
的赋值确实在创建新实例,但您传递的是原始包装函数 spam
的相同实例。修饰后,spam
不再是一个函数,而是tracer
的一个实例。这就是 Python 设计来处理对象周围 class 的包装的方式:对象的名称成为包装对象的实例。
任何时候创建 tracer(spam)
,tracer
中的属性 func
都是原始包装函数 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__
,从而在 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)
您的问题是 t
和 g
是双重包裹的示踪剂。也就是说,它们是一个 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
包裹它),并且 t
和 g
将直接应用于该函数,而不内心的 tracer
妨碍了。