DEV Community

kaelscion
kaelscion

Posted on • Edited on

Py in 5: Decorators

py-in-5-decorators-cover

originally published on the Coding Duck blog: www.ccstechme.com/coding-duck-blog

Hello everybody and welcome to another Py in 5 discussion. The article series where I take a Python programming concept and try to explain it in human terms in a 5 minute read or less. Our topic today: Decorators!

As is our custom, let's get the official definition of decorators:

pythontips.com:

Functions which modify the functionality of other functions

python.org:

A function returning another function applied as a function transformation using the @wrapper syntax

Okay, so what I'm hearing is....functions. Functions that take a function to return a function so that your app functions functionally. And the programming paradigm used? Object Oriented...obviously.

Even though these definitions aren't at all talking in circles or using their own definition to define themselves, I'm going to try to clarify things anyway.

In a really abstracted sense, think of decorators as a kind of encapsulation, but on the function level rather than the class level. Just like classes in Python allow for encapsulation of their properties, decorators allow this OOP concept within functions. Kind of like when you import a class into your python file, let's say the "time" module and instantiate (or create an instance of) the time class. And let's say you wanted to tell your program to wait 10 seconds before continuing. For this, you would write time.sleep(10). What you are basically doing here is leveraging encapsulation. The "sleep" function is encapsulated (or enclosed) in the time class.

Think of it as if you had a friend name Sleep you wanted to play with as a kid. You would go to his house, ring the bell, and his dad Time would answer the door. "Hi! Can Sleep come out and play?" you would ask Time.

"Sure!" he would respond, "Let me go get him for you." Then, Sleep would come out and you would go have a jolly old time for yourselves. A decorator provides a similar functionality, but rather than encapsulating functions, it encapsulates context. Any function that is called with a decorator, gets access to the context (stuff that happens before and after the function runs that affect the function as it runs) that is unique to that decorator. Kind of like your parents (the class) would have a particular way you needed to behave in a general sense. But when your uncle (the decorator) would take you to do stuff, your behavior would be different than it was at home. What was expected of you, and therefore how you acted, was different while you were under the temporary care of a different relative than your parents.

For example, lets take a look at a few decorators that change the behavior of an html form. The form is written as follows and rendered in the Flask web framework:


@app.route('/')
def styling():
    return '''
            <form action='' method='post'>
                <dl>
                  <dt>Username:
                  <dd><input type=text name=username>
                  <dt>Password:
                  <dd><input type=text name=password>
                  <dd><input type=submit value=Login>
                </dl>
            </form>'''
Enter fullscreen mode Exit fullscreen mode

and renders like this

base-rendering-image-no-decorators

Now, let's add the following decorators to the mix:


def style_with_flex_end(func):
    def func_wrapper():
        return '''<div style="display: flex; flex-direction: row; justify-content: flex-end">{0}</div>'''.format(func())
    return func_wrapper

def style_with_flex_center(func):
    def func_wrapper():
        return '''<div style="display: flex; flex-direction: row; justify-content: center">{0}</div>'''.format(func())
    return func_wrapper

Enter fullscreen mode Exit fullscreen mode

Both decorators do similar things. The only difference between them is the justify-content style tag. However, by simply giving the aforementioned html form the context these decorators apply, we get a different result. Lets attach the style_with_flex_end decorator to this Flask route simply by calling it with an "@" symbol and no parentheses above the definition of the "styling" function.


@app.route('/')
@style_with_flex_end
def styling():
    return '''
            <form action='' method='post'>
                <dl>
                  <dt>Username:
                  <dd><input type=text name=username>
                  <dt>Password:
                  <dd><input type=text name=password>
                  <dd><input type=submit value=Login>
                </dl>
            </form>'''

Enter fullscreen mode Exit fullscreen mode

Adding the decorator renders that same form like this:

rendering-form-with-flex-end-decorator

As you can see, the justify-content: flex-end; context provided by the decorator changed where the form appeared on the page without need to modify the form code itself. Same thing can be done with the our other decorator:


@app.route('/')
@style_with_flex_center
def styling():
    return '''
            <form action='' method='post'>
                <dl>
                  <dt>Username:
                  <dd><input type=text name=username>
                  <dt>Password:
                  <dd><input type=text name=password>
                  <dd><input type=submit value=Login>
                </dl>
            </form>'''

Enter fullscreen mode Exit fullscreen mode

which then renders as follows:

form-rendering-flex-center-decorator

So, in summary, a decorator adds context to a function. It, basically, gives your function "permission" to use the added window dressing that the decorator provides. Our example shows that if we ask the @style_with... decorators if the justify-content style they implement can "come out to play", they will give our html a different set of expectations than when they are "at home with mom and dad", therefore allowing them to behave differently than they normally would.

For a deeper dive into Decorators, check out this SO post that was brought to my attention by @rrampage (thanks so much for sharing, Raunak) which can be found here

I hope this has cleared some things up for those of you who were unsure of what a decorator was or what it did. Please leave a comment below if you have any questions or need clarification! I am always willing to help others understand because the world needs you, your ideas, and your unique approach to this wonderful field so that we can all build the cool, useful, awesome tech of tomorrow!

Top comments (5)

Collapse
 
rrampage profile image
Raunak Ramakrishnan

Great article. Decorators are very useful for writing reusable, boilerplate-free code.
This incredible answer from Stackoverflow taught me about the power and utility of decorators.

Collapse
 
kaelscion profile image
kaelscion

That answer is the very summation of the research I did on this post and is extremely comprehensive. With your leave, I would like to add that link to the main article as a deeper dive follow-up source material. Is that alright with you?

I've used decorators for a long time but never started to write my own until a year or so ago. On conception of this article series (Py in 5), I decided to take a deeper look at all of the Pythonic (and programming in general) topics, tips, tricks, and features that I more or less took for granted, and see if I could explain the "under the hood" enough for understanding in 5 minutes or less. This would force me to gain a more complete understanding of the topics myself. That SO post is very similar to the type of material I referenced to gain said thorough knowledge and I thank you so much for sharing it as well as for giving my article a bit of a review! πŸ˜„πŸ˜„πŸ˜„

Collapse
 
rrampage profile image
Raunak Ramakrishnan

Feel free to share it. The more people who can read it, the better!

Collapse
 
tobiassn profile image
Tobias SN

I’d just like to note that in Python, modules aren’t the same as classes. You import a module, but you create an instance of a class.

Collapse
 
kaelscion profile image
kaelscion

Great point! This is an important distinction in the understanding the official use of these terms in Pythons context and what they actually mean vs how they can be used. It appears I have fallen prey to using the terms interchangeably. Thank you for pointing that out!