avatarJordan P. Raychev

Summary

The webpage content provides an in-depth explanation of Python decorators, detailing their purpose, usage, and implementation, with a focus on creating decorators with arguments to enhance code reusability and maintainability.

Abstract

The article titled "Python Decorators Made Easy, vol2" delves into the concept of Python decorators, a powerful design pattern that allows developers to add functionalities to functions and methods without altering the entire codebase. It begins by illustrating a common scenario in web development where a function responds to user requests, and then introduces the challenge of restricting content access to users from whitelisted domains. The author demonstrates how to implement a decorator to solve this problem efficiently, avoiding code duplication and adhering to the DRY (Don't Repeat Yourself) principle. The article progresses to more advanced topics, including the creation of decorators that accept arguments, which adds flexibility and reusability to the decorator functionality. By the end of the article, the reader gains a clear understanding of how to build and apply decorators in Python, with practical examples that reinforce the concepts discussed.

Opinions

  • The author suggests that while it's possible to implement new functionalities without decorators, doing so leads to code that is prone to errors and is not scalable.
  • Decorators are praised for their ability to make the codebase cleaner, more maintainable, and less error-prone by reducing repetitive code snippets.
  • The article emphasizes the importance of the DRY principle in software development, which is well-served by the use of decorators.
  • The author expresses that Python's decorator design pattern is not only simple and clean but also clever, highlighting the language's elegance in solving common programming problems.
  • By introducing decorators with arguments, the author conveys that this advanced technique enhances the flexibility and applicability of decorators in various scenarios.

Python Decorators Made Easy, vol2

Python decorators is one of these topics that you certainly know how to use but it may be not so obvious how to build one. In this article I will try to explain what a decorator is, how to use and build one. At the end of the article we will go over decorator with arguments and how they differ from standard ones.

Photo by Chris Ried on Unsplash

Problem introduction

A decorator is a design pattern in Python which allows us to add more functionalities to functions and methods without changing the entire code base. What does that mean?

Suppose you have the following function, that returns some content to the user requesting it. Such function are commonly found in web frameworks like Django and Flask.

def main_view(request):
    return "Some content pulled from database"

Function looks a bit abstract written in this way but bear with me, we we will get there. When a user hits a particular URL, that URL is mapped to a function aka view that returns some content (most of the time, that content is pulled from some sort of a database). Since we don’t have a running application and cannot make a “real” request, we have to build one. Keep in mind that we are not going to build a full HTTP request but a mock one that has only two fields — user and email. Here is a look at our request.

request = {
    'user' : 'Jordan',
    'email' : '[email protected]',
}

When our view gets hit by a random user regardless of his status, some content from the database will be returned to him. What we want to do in this scenario is build a functionality that allows only whitelisted domains to be able to retrieve that content. Sounds easy enough right? Lets first build our whitelist.

# whitelisted domains
whitelist = ['example.com', 'example.net', 'example.org']

Once we have a whitelist in place, lets check if the users email is whitelisted and if it does we will return the content to him. Otherwise we will raise a permission error saying that the domain is not whielisted.

def main_view(request):
    domain = request.get('email').split('@')[1]
    if domain in whitelist:
        return "Some content pulled from database"  
    raise PermissionError('Your domain is blacklisted')

Since our user’s email is registered on example.com domain (which is whitelisted), the content is returned to him and if that is not the case an error raised.

>>> python .\main.py
'Some content pulled from database'
>>> python .\main.py
Traceback (most recent call last):
  File "main.py", line 32, in <module>
    print(main_view(request))
  File "main.py", line 30, in main_view
    raise PermissionError('Your domain is blacklisted')
PermissionError: Your domain is blacklisted

Everything seems to be working as expected with one view. Imagine now that you have 20–30 or even 100 views in our application. Should you paste that snippet of code in each of them? Nope of course. Python’s DRY principle encourages you not to repeat yourself and that is what we are about to do.

Decorators in actions

We saw that we are able to implement new functionalities without decorators but as I already mentioned if you need to re-use that snipped of code in other function it because just one hell of copy and paste. The code base starts to look ugly, the code is prone to mistakes during that process, an overall hell in my opinion. Let’s modify our code and transform it to a decorator.

def is_auth(func):
    def wrapper(inner_request):  
        domain = inner_request.get('email').split('@')[1]
        if domain in whitelist:
            return func(inner_request)
        raise PermissionError('Your domain is blacklisted')
    return wrapper

Our view is exactly as we have written it in the beginning, but we are adding the decorator on top of it to well… decorate the function

@is_auth
def main_view(request):
    return "Some content pulled from database"

When we execute our main_view function, we are getting the same result as before but without changing one line of our initial function.

>>> main_view(request)
'Some content pulled from database'

How did we get here you may ask. Well, that is the beauty of python decorators — its simple, clean and very clever design pattern. Lets dive in a bit more into it.

First we got a function called is_auth which is our main decorator. Since decorators are wrappers of another function we pass a function as argument to that decorator. The main_view function arguments are passed to a wrapper function inside the decorator. At that point we simple extract the domain from the passed dictionary (request argument) and check if that domain is whitelisted. If it does we simply return the content of main_view function. Otherwise we raise a PermissionError to indicated that user’s domain is blacklisted.

In the beginning I said that we are going to cover more advanced topic such as passing arguments in the decorator. Going back to our example, instead of calling the whitelist list inside our wrapper function, we could pass it as an argument through the decorator function. Our decorated function is going to look like this.

def is_whitelisted(inner_list):
    def decorator(func):
        def wrapper(inner_request):
            domain = inner_request.get('email').split('@')[1]
            if domain in inner_list:
                return func(inner_request)
            raise PermissionError('Your domain is blacklisted')
        return wrapper
    return decorator
@is_whitelisted(whitelist)
def main_view(request):
    return "Some content pulled from database"

Passing arguments through decorator consist of wrapping the decorator in yet another function. The difference is that in the first decorating function, we are passing decorator’s arguments, in the second decorating function we are passing the decorated function and in the third decorating function (wrapper) we are passing the arguments of decorated function.

When we execute the code we get exactly the same output.

python .\main.py
'Some content pulled from database'

If another request comes form a user that belongs to a domain that is not whitelisted we would get an error.

request = {
    'user' : 'John',
    'email' : '[email protected]'
}
>>> python .\main.py
Traceback (most recent call last):
  File "main.py", line 24, in <module>
    print(main_view(request))
  File "main.py", line 16, in wrapper
    raise PermissionError('Your domain is blacklisted')
PermissionError: Your domain is blacklisted

Conclusion

In this article we have go through what python decorators are and how we can use them in a certain project. The topic may seem a bit more advanced and does not cover all aspects of decorator function. If you want to read a bit more about it, please check out another article of mine where that topic is covered. As always I hope that this has been informative to you and I would like to thank you for reading.

Happy decorating!

Python
Python3
Tutorial
Decorators
Design Patterns
Recommended from ReadMedium