Tips and Tricks - Flexible Decorators

Since Python 3.9, decorators are a lot more flexible than before. Any callable can now be a decorator. With that being said, a couple of (seen from the perspective of a Python 3.8 developer) crazy things can be done.

Suppose we have three decorators transforming the returned text of a given function:

# voices.py
import functools


def normal(func):
    return func


def shout(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs).upper()

    return wrapper


def whisper(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs).lower()

    return wrapper

From Python 3.9 onwards, we can put all three into a dictionary, let the user decide, which one to use, and apply it to a given function as follows:

# previous code in voices.py
DECORATORS = {
    "normal": normal,
    "shout": shout,
    "whisper": whisper,
}

voice = input(f"Choose your voice ({', '.join(DECORATORS)}):")


@DECORATORS[voice]
def get_story():
    return "This is a sample text."


print(get_story())

Executing the script at hand gives us the following output:

$ python voices.py
Choose your voice (normal, shout, whisper): shout
THIS IS A SAMPLE TEXT.

Groups: language reference