Can someone explain this decorator code to me?

The code was taken from Learning Python 4th Edition by Mark Lutz

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) 

In addition, when I run this code, it also does not print the sum of 1,2,3, but in the book he showed that it is so! I left scratches on this whole code. I have no idea what is going on here.

+6
source share
2 answers

What happens here is a replacement for the function body. Such a decorator

 @tracer def spam(...) ... 

It is equivalent to:

 def spam(...) ... spam = tracer(spam) 

Now tracer(spam) returns an instance of the tracer class, where the original spam definition is stored in self.func

 class tracer: def __init__(self, func): #tracer(spam), func is assigned spam self.calls = 0 self.func = func 

Now, when you call spam (which is actually an instance of tracer ), you call the __call__ method defined in the tracer class:

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

So this __call__ method overrides the body defined in spam . For the body to execute, you need to call the function stored in the tracer class instance as follows:

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

Result

 >>> call 1 to spam 6 
+7
source

It is strange that you are not doing anything with *args . Is it possible that the __call__ line is __call__ ?

 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__)) return self.func(*args) 

Recall that decorator syntax is another way of saying this.

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

therefore, spam becomes an instance of the tracer class and spam is passed to the __init__ method and stored in self.func

Now, when spam is called, you call this instance of the tracer, so the __call__ method is __call__ .

Usually do nothing __call__ wrapper

 def __call__(self, *args, **kw): return self.func(*args, **kw) 

I don’t know why he didn’t include support for keyword arguments, but ignoring this, you see that there are only two additional lines that are run every time an instance of the tracer is called.

0
source

Source: https://habr.com/ru/post/950220/


All Articles