Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Locked learning resources

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Registering Plugins With Decorators

In this lesson, you’ll learn how to create a simple plugin architecture using 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 PLUGINSwe’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.

Avatar image for Geir Arne Hjelle

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.

Avatar image for Vincent De Haen

Vincent De Haen on Jan. 9, 2021

Great video. LOLed on the “The func that comes in” :D

Avatar image for Bastian

Bastian on Sept. 10, 2021

This example made me understand something I did not grasp at first sight: it looks like the decorator gets executed when we define the decorated function.

(I thought the decorator was executed when we executed the decorated function.)

Avatar image for Geir Arne Hjelle

Geir Arne Hjelle RP Team on Sept. 10, 2021

@Bastian, right. The decorator (meaning the function with the exact same name used behind the @ when decorating) is executed when the decorated function is defined.

In the most typical decorator pattern with the inner function, this decorator then defines a new (inner) wrapper function that is executed when the decorated function is executed. But simple decorators like the one in this video only run on “define-time”.

Avatar image for Bastian

Bastian on Sept. 12, 2021

Ho yes thanks @Geir Arne Hjelle for reminding me that a typical decorator pattern is executed only to define a new wrapper function. This is all so logic and yet feels so counter intuitive to me. I think I get it technically but I still need to get around the concepts and use cases.

Avatar image for Konstantinos

Konstantinos on Sept. 3, 2022

In the last real-world example, the decorated functions become elements of a dictionary and this is described as “registering plugins”. I cannot understand where this applies, thus becoming a real world example.

Avatar image for Geir Arne Hjelle

Geir Arne Hjelle RP Team on Sept. 5, 2022

@Konstantinos Plug-ins gives you an architecture where you can more easily “dynamically” decided which function to call. I use this a lot in my own real world code both for flexibility and because it keeps my code simple.

For a concrete example, pyconfs is my library for working with configurations. It separates “configuration” from “file format” for flexibility. In other words, most of the library doesn’t care or even know whether it’s working with TOML, YAML, or JSON.

This is done by defining readers as plug-ins that are used to load the configuration into memory. If you want to support a new input format, that’s as easy as writing the function that parses that format into memory and decorating that function with @register. You don’t need to change any other part of the code, because the rest of the code only works with the dictionary of registered functions.

The same pattern can be used in many other real world examples. For example, you can register different models to use when calculating satellite orbits, different stages to use in a machine learning model, different windows or tabs to expose in a GUI, and so on.

Another example could pull platform specific code into plug-ins, so that you’d call some code on Windows and another on Linux. Again, this would allow you to add support for new platforms without needing to change the existing code.

I talk a bit more about this principle in this presentation: pyvideo.org/pycon-us-2019/plugins-adding-flexibility-to-your-apps.html

Avatar image for Konstantinos

Konstantinos on Sept. 6, 2022

Thank you @Geir Arne Hjelle for answering that in detail. So, in a high level, I understand that the registered functions should always have the same objective such as “read-file-formats”, “calculate-satellite-orbits”, etc that vary in implementation. It seems like an interesting technique, but needs some practice in my real world situations.

Avatar image for Geir Arne Hjelle

Geir Arne Hjelle RP Team on Sept. 6, 2022

@Konstantinos Yes, exactly. It typically works most efficiently when grouping together functions with the same purpose. Usually you want your registered functions to even share the same signature so that you don’t need “extra knowledge” to call them.

Avatar image for Konstantinos

Konstantinos on Sept. 6, 2022

@Geir Arne Hjelle Thank you very much for your concise and clear explanation.

Become a Member to join the conversation.