In this lesson, you’ll learn how to create a simple plugin architecture using decorators.
Registering Plugins With Decorators
00:01
In this last example, I’ll show you how you can register functions using a decorator, sort of like registering plugins. For this final example, I’m going to have you work just inside the REPL. Start by importing random
.
00:16
You’ll see why in a moment and how you’re going to use it. Make a dictionary named PLUGINS
. Make a decorator. Its name is register()
.
00:28
It’s going to look very different from the ones that you’ve made up to this point—a little simpler. It will have a docstring saying that it’s designed to """Register a function as a plug-in"""
.
00:39
Using PLUGINS
, take that func
that comes in—”Yeah, that funk that comes in”—and using the .__name__
method, add it to functions.
00:52
Then, return the function unchanged. Great. There’s register()
.
01:00
Now use that as a decorator as you define… These are functions that you created a long time ago. say_hello()
takes a name
as an argument and returns an f-string, with a very simple f"Hello "
, and name
as the expression.
01:19
There’s say_hello()
. And register this one also. It’s called be_awesome()
. It takes a name
as an argument and this time returns an f-string with the expression name
in it.
01:40
All right. What’s inside PLUGINS
now? PLUGINS
now has two functions inside of it as the dictionary: say_hello
and be_awesome
. Pretty cool! Well, how are you going to use it? Next, you’re going to make a function called randomly_greet()
.
01:59
It takes a name
as an argument.
02:05
A greeter and a greeter function—here’s where we’re using random
. With the method choice()
it’s going to pull out of PLUGINS
—we’ll make it into an iterator by using the method .items()
. Close off all those parentheses.
02:21
So, random.choice()
out of the PLUGINS
dictionary to populate greeter
and greeter_func
. Using print()
, make an f-string saying f"Using "
which greeter
in its repr()
version. Close off the print statement. And then return with a call to that greeter()
function, with the name
that has been passed in. All right!
02:48
I’ll make some space. Now, randomly_greet()
is available and let’s say you want to greet "Alice"
. So here, it’s saying that it’s using the greeter function 'be_awesome'
, so it randomly picked that one. Try it again. Now it used 'say_hello'
.
03:07 Pretty neat! So you could have multiple functions that you’re registering in to create this simplified plugin architecture.
03:17 The main benefit of this simple architecture is that you don’t actually have to maintain a list of which plugins exist. The list gets created every time you register a plugin by applying the decorator to any of those functions, which is pretty neat! Okay.
03:33 It’s time to wrap up Decorators 101 with a final review.

Geir Arne Hjelle RP Team on May 31, 2020
Hi Sam,
the different pattern used here for @register
can be used when your decorator only needs to do something when the decorated function is defined, in this case it’s registered to a dictionary.
Since we’re not changing the function that has been passed in, that function will work exactly as if it had not been decorated. In the usual pattern, the decorated function is instead replaced by the inner wrapper function.
If you use your simplified @slow_down
decorator, you will notice that the code sleeps for 1 second whenever you define a new decorated function, but it will not sleep when you call that decorated function.
Sam W on June 1, 2020
Oh of course. Seems obvious now you say it. Ok, thank you.
levan8421 on Sept. 29, 2020
I think there is no need to have return func
in the def register(func)
. Correct me if I am wrong. Thanks

Geir Arne Hjelle RP Team on Sept. 30, 2020
@levan8421 A decorator replaces the function being defined with what’s being returned from the decorator. If you don’t return func
, the decorator is implicitly returning None
. That would mean that the decorator would replace any function it decorates with None
.
For example:
>>> def register(func):
... print(f"registering {func}")
...
>>> @register
... def hello(name):
... return f"Hello {name}"
...
registering <function hello at 0x7f432a989200>
>>> print(hello)
None
>>> hello("world")
TypeError: 'NoneType' object is not callable
levan8421 on Oct. 6, 2020
Thank you for your response. I understand why decorator needs to return the func in general. I am just talking about this particular example of registering plugins:
import random
PLUGINS = dict()
def register(func):
PLUGINS[func.__name__] = func
@register
def add(x):
return x+5
def random_cal(x):
func_name, func = random.choice(list(PLUGINS.items()))
print(f'*** Using {func_name}')
return func(x)
In the decorator **def register(func)**
, the func is already added to PLUGINS and that is the sole purpose of this decorator. Later when we call the random_cal
, the registered function will be called from the PLUGINS dictionary. That is why I think there would be no need to have *return func*
in the def register(func)
. Of course, I would agree if it is for convention purpose.

Geir Arne Hjelle RP Team on Oct. 6, 2020
@levan8421 True. In that particular example it works without returning the function, because you are only referencing the registered function through the PLUGINS
dictionary.
However, it seems bound to cause issues later to have the decorator make the function uncallable directly, so I would still highly recommend returning func
- even if your current code doesn’t use it.
Vincent De Haen on Jan. 9, 2021
Great video. LOLed on the “The func that comes in” :D
Become a Member to join the conversation.
Sam W on May 31, 2020
I think I’ve missed something already explained. I wanted to ask about the different form of decorator here. Why does it not have include the inside wrapper function?
And now I’m wondering why we can’t do similar to the above all the time.
e.g.
Why can’t a decorator from before, such as:
not instead just be: