I am new to python decorators. I have understood the basic concepts with the help of simple examples. But when I tried to read this more practical decorator, I feel lost. Given below is the code followed by my questions:
class countcalls(object):
"Decorator that keeps track of the number of times a function is called."
__instances = {}
def __init__(self, f):
self.__f = f
self.__numcalls = 0
countcalls.__instances[f] = self
def __call__(self, *args, **kwargs):
self.__numcalls += 1
return self.__f(*args, **kwargs)
def count(self):
"Return the number of times the function f was called."
return countcalls.__instances[self.__f].__numcalls
@countcalls
def f():
print 'f called'
f()
f()
f()
print f.count() # prints 3
My doubts:
When we prefix the decorator to a function, does that mean that we are creating an object of the decorator class right there? In our case, when it says:
@countcalls
def f():
print 'f called'
Is @countcalls
equivalent to creating a countcalls
object and passing the function below to its __init__
method?
The __call__
is taking three arguments. self
is fine as far as the question above is answered. What the hell are the two other arguments: *args, **kwargs
and what are they achieving?
How can I get better at decorators?
A decorator is a design pattern in Python that allows a user to add new functionality to an existing object without modifying its structure. Decorators are usually called before the definition of a function you want to decorate.
Decorators provide a simple syntax for calling higher-order functions. By definition, a decorator is a function that takes another function and extends the behavior of the latter function without explicitly modifying it.
When to Use a Decorator in Python. You'll use a decorator when you need to change the behavior of a function without modifying the function itself. A few good examples are when you want to add logging, test performance, perform caching, verify permissions, and so on.
Decorator is a structural pattern that allows adding new behaviors to objects dynamically by placing them inside special wrapper objects, called decorators. Using decorators you can wrap objects countless number of times since both target objects and decorators follow the same interface.
This code seems to have some oddness. Let's talk about the slightly-simpler code
class countcalls(object):
def __init__(self, f):
self._f = f
self._numcalls = 0
def __call__(self, *args, **kwargs):
self._numcalls += 1
return self._f(*args, **kwargs)
def count(self):
return self._numcalls
@countcalls
def f():
print 'f called'
f()
f()
f()
print f.count()
# output:
# f called
# f called
# f called
# 3
Remember
@countcalls
def f():
print 'f called'
is the same thing as
def f():
print 'f called'
f = countcalls(f)
so when we use the decorator, the function is stored using the _f
attribute.
So f
is a countcalls
instance. When you do f(...)
you call f.__call__(...)
—that's how you implement ()
syntax for your own instances. So when you call f
, what happens?
def __call__(self, *args, **kwargs):
self._numcalls += 1
return self._f(*args, **kwargs)
First, you use *args
and **kwargs
, which in the definition condense all positional and keyword arguments into a tuple and dict, and later in the call expand a sequence and a dict into arguments (see 4.7.4 in the official tutorial for more information). Here's a partial example
>>> def f(*args): print args
...
>>> f(1, 2)
(1, 2)
>>> f()
()
>>> def add(a, b): return a + b
...
>>> add(*[4, 3])
7
>>> add(**{'b': 5, 'a': 9})
14
so def f(*args, **kwargs): return g(*args, **kwargs)
just does a passthrough on all arguments.
Aside from that, you're just keeping track of how many times you've been in __call__
for this instance (how many times you've called f
).
Just remember that @dec
def f(...): ...
is the same as def f(...): ...
f = dec(f)
and you should be able to figure out most decorators fine, given enough time. Like all things, practice will help you do this quicker and easier.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With