Who Are You, Really?
In this lesson, you’ll learn what introspection is and how it can be used to get more information about a Python function. You’ll also see that introspecting decorated functions can be a little confusing because of the way the functions identify themselves after decoration. To solve this problem, you will learn how to use @functools.wraps
, a decorator from the functools
module which updates special attributes used for introspection.
00:00
One of the great conveniences when working with Python is the ability to use an interactive shell. It could be the one that comes with Python—the REPL—or it could be another one that you’ve added on, like the one I’ve been demonstrating throughout this course, called bpython
.
00:17 You can use its powerful introspection ability. Introspection is the ability for an object to know what its own attributes are at runtime. As an example, a function knows its own name and documentation.
00:30
Let me show you what I’m talking about. Throughout the course, you’ve been practicing introspection by entering in the names of functions and pressing Return without calling them. Something like print
.
00:43
You’ll see here that print()
is a function
. It’s a built-in function
—it’s part of the standard library—and its name is print
.
00:52
To confirm the name, you can also use a double underscore (__
) or dunder method named .__name__
. So print.__name__
, and again at the end, the double underscore. print.__name__
prints out its name: 'print'
.
01:10
Another type of introspection that you could do is to use help()
on an item. The help()
function allows an argument, and you can put a function into it.
01:18
So, help(print)
will show you a little bit about the documentation that was created when creating this function. It tells you about what type of arguments it can use. To get out of help, you can press Q—just the letter Q by itself. Okay.
01:34 Let’s go back to talking about the decorator that you defined, and then the functions that get wrapped by it.
01:41
To help with all the typing that we’ve been doing, let’s create a new file and call it examples.py
. examples.py
is where we’re going to place the example functions that we can then import into the REPL as we’re using it.
01:57
So back here in decorators.py
, you can see that we have our wrapper, so in examples.py
, I’ll have you import, from decorators import do_twice
.
02:09
Great. Now you can use it! To decorate, use your @
symbol, @do_twice
. Let’s redefine our old friend say_whee()
, which prints everyone’s favorite.
02:24
Okay. To look at this back here in the REPL, from examples import say_whee
. Great! Here it is. And you can run it. And can you inspect it? Yeah. Type its name like you’ve done before.
02:46
This is where I wanted to focus for this lesson. This is confusing. It’s saying that it’s a reference to do_twice
, the local scope, and it’s inside of a wrapper named wrapper_do_twice
—but I thought it was called say_whee
. Okay, well, what if you use the method .__name__
? What does it say its name is here?
03:06
It’s mentioning the wrapper inside of our decorator. And the same with help()
.
03:12
Again, a mention of the decorator. In fact, it says Help on function wrapper_do_twice in module decorators
. Press Q. This can be confusing, especially if someone else is looking through your code.
03:23 Even for yourself, to revisit code later on, especially if you’ve wrapped and used a handful of decorators inside of your code—they’re all going to reference the wrapper inside of your decorator. Well, there’s a nice tool that helps you with this.
03:38
To fix this, decorators should use the @functools.wraps
decorator. It preserves the information about the original function. Okay. How does it work? Back here, under decorators.py
—
03:55
let me close your file explorer, there—import functools
at the top of decorators.py
. Then, once functools
is imported, add a new line, the decorator @functools.wraps()
, that takes a function as an argument. That’s it.
04:21
Go ahead and save. Once again, you need to restart your REPL session, so exit out and then come right back in. The great thing about creating that examples.py
module is now you can import the function you want to use again.
04:41
Let’s see if you can call say_whee()
. It looks like it works—good!
04:48
What if you inspect it? Again, just typing its name. Wow, that’s a big change. Now it’s saying the function is say_whee
, and then its memory location. That’s like way back when we started talking about functions at the beginning. Great! That’ll be less confusing.
05:06
And now, what if you reference .__name__
? It shows a string, 'say_whee'
, which is great.
05:15
And if you call help()
—looks great! It’s now showing its true location back in module examples
, as opposed to the decorators
module that is imported into examples
.
05:29 Again, press Q to exit help. Much better! Now, your functions will remain themselves after decoration. With completing this piece on introspection, you’re ready for a little review and then on to some real-world examples.
Geir Arne Hjelle RP Team on March 26, 2019
Hi Dirk,
it’ll be a good practice to use @functools.wraps
when you create your own decorators. One exception is when you return the original function untouched as is done in the Registering Plugins With Decorators example.
Also, there is the functools.update_wrapper()
function which @functools.wraps
uses under the hood. There are some cases when calling the function directly gives you some added flexibility. One example is when your decorator is implemented as a class instead of a function: realpython.com/primer-on-python-decorators/#classes-as-decorators
Dirk on March 26, 2019
Thanks Geir!
Become a Member to join the conversation.
Dirk on March 26, 2019
Should I always import functools whenever I use decorators?