Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

May someone explain this decorator code to me?

Tags:

python

The code has been 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)

Also, when I run this code, it doesn't print the sum of 1,2,3 either, but in the book, it's shown that it does! I am left scratching my head at this entire code. I have no idea what is going on in here.

like image 341
Always Learning Forever Avatar asked Jul 25 '13 01:07

Always Learning Forever


People also ask

When a decorator is called?

A decorator is called as soon as the decorated function is defined. It is equivalent to writing something like this: def __do_stuff(... ): ...

What is decorator in Python stackoverflow?

A decorator is a function that takes another function and extends the behavior of the latter function without explicitly modifying it. Python allows "nested" functions ie (a function within another function). Python also allows you to return functions from other functions.


1 Answers

What's happening here is the body of the function is being replaced. A decorator like so

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

Is the equivalent of:

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

Now, tracer(spam) returns an instance of the tracer class where the original definition of spam 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 invoke 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 in essense this __call__ method has overriden the body origianlly defined in spam. To have the body execute, you need to call the function stored in the instance of the tracer class like so:

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

Resulting in

>>> 
call 1 to spam
6
like image 62
HennyH Avatar answered Oct 13 '22 03:10

HennyH