In the preceding mission the author drew a parallel between decorators and nested functions:
In order to understand what is going on here, we’ll abandon decorators and look at the version of the code analogous to the one on the code blocks on the left above.
But before we do this, let’s take a quick look at adding attributes to an instance of an object.
Expand to explore this topic
In Python, we sometimes are allowed to add attributes to objects, and sometimes we aren’t:
>>> x = 3
>>> x.aly = "Aly owns this attribute."
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute 'aly'
We got an error — we can’t always do this. But sometimes we can:
>>> def aly_function():
... return "This is a great function. The best"
>>> aly_function.aly = "Aly owns this attribute."
'Aly owns this attribute.'
We won’t get into the details now, suffice to say that you can do this with functions.
Getting back to your question, let’s now create an equivalent version of the given code without decorators:
>>> def counter(func):
... def wrapper(*args, **kwargs):
... wrapper.count += 1
... # Call the function being decorated and return the result
... return func(*args, **kwargs)
... wrapper.count = 0
... # Return the new decorated function
... return wrapper
>>> def foo():
... print('calling foo')
Notice that we didn’t add a decorator on top of
foo's definition. Also note that there is a line that creates an attribute for
wrapper.count = 0), just like we saw above.
Now let’s create a new function called
new_foo, that will mimic
foo when preceded by the decorator
@counter — like in the mission
>>> new_foo = counter(foo)
Let’s think about what this function is. It is the result of calling
counter with the argument
foo. What does
counter do? It does three things:
It creates a function
wrapper that adds
1 to the value of
wrapper.count and returns the result of calling
foo. In other words, it creates a function that is pretty much foo, only it adds
1 to the
It also creates an attribute in the above function with the value of
0. This will make sure that whenever the line
wrapper.count += 1 is ran for the first time, there actually is a value in
wrapper.count to which we can add
Finally it returns the
counter creates a version of
foo with an attribute
count initialized with the value
0, with the property that whenever the function is ran, this counter will be incremented by one. Let’s verify this:
As we can see,
0. Now let’s call this function and recheck the counter:
It seems to be working. Let’s call it once again:
Yep! It’s working just as described above. One difference between what we did here and the examples, is that we didn’t overwrite
foo, but instead created a new function (
new_foo). The examples would have worked just the same if we had ran
foo = counter(foo) and replaced
I hope this helps.