DEV Community

Abe Garcia
Abe Garcia

Posted on • Edited on

Working With Decorators In Python

In my last post, I wrote about functional closures in Python and I mentioned that the most interesting thing about them is that they can be used to create decorators. And they do just that, they decorate existing functions with additional functionality, and they're also reusable so they can be applied to multiple different functions at once.

Decorators Defined

A decorator is essentially a higher order function that both accepts a function as an argument and also returns a function. The result is a function that has the same functionality as the one you passed into the decorator along with whatever extra functionality you defined within the decorator body. This is best explained through a code example:

def example_func():
    print("I'm a function!")

def decorator(func):
    def inner():
        func()
        print("I've been decorated!")
    return inner

Now we can say:

decorated_func = decorator(example_func)
decorated_func()

Now if we run the program, the output will be:

I'm a function!
I've been decorated!

Decorators actually have a cool bit of syntactic sugar where instead of typing this:

decorated_func = decorator(example_func)
decorated_func()

We can just add @decorator above the decorated function's definition like so:

@decorator
def example_func():
    print("I'm a function!")

Now if you call example_func(), the output will be the same as it was previously.

Decorating A Function That Takes Arguments

Decorators can also be applied to a function that takes in parameters, and the decorator will actually have access to any parameters passed into the decorated function.

Take this function:

def capitalize_first_name(func):
    def wrapper(first_name,last_name):
        capitalized = first_name.upper()
        func(capitalized,last_name)
    return wrapper

@capitalize_first_name
def print_name(first_name,last_name):
    print(f"Hi, my name is {first_name} {last_name}")

Now, for example, if we say print_name("John", "Doe"),
the output will be Hi, my name is JOHN Doe! without us ever having to alter the original function.

And you don't always need to have the same number of arguments for a decorator to work; you can pass the *args operator into the wrapper and be able to work with any function with any number of arguments!

Conclusion

Decorators are, in my opinion, one of the coolest features that Python has to offer. You don't always have a good reason to use them, and you probably shouldn't unless you actually have to, but they can be incredibly useful when the situation calls for them. Decorators are highly used in web frameworks like Flask and Django to add HTTP routing functionality, authentication, and more.

Top comments (3)

Collapse
 
danialmalik profile image
Danial Malik

This is a great post, but I think that in your first output example the correct sequence of outputs should be :

I'm a function!
I've been decorated!
Collapse
 
abe21412 profile image
Abe Garcia

You're absolutely right! I've switched the lines in the post

Collapse
 
jmplourde profile image
Jean-Michel Plourde

I use decorators in Django for mocking functions (@patch). It's very useful and easy to implement. Now I better understand the inner working. Thanks for that awesome post.