What is the purpose of decorators (why use them)?

Question:

I’ve been learning and experimenting with decorators. I understand what they do: they allow you to write modular code by allowing you to add functionality to existing functions without changing them.

I found a great thread which really helped me learn how to do it by explaining all the ins and outs here: How to make a chain of function decorators?

But what is missing from this thread (and from other resources I’ve looked at) is WHY do we need the decorator syntax? Why do I need nested functions to create a decorator? Why can’t I just take an existing function, write another one with some extra functionality, and feed the first into the 2nd to make it do something else?

Is it just because this is the convention? What am I missing? I assume my inexperience is showing here.

Asked By: Erich Purpur

||

Answers:

From PEP 318 — Decorators for Functions and Methods (with my own added emphasis):

Motivation

The current method of applying a transformation to a
function or method places the actual transformation after the function
body. For large functions this separates a key component of the
function’s behavior from the definition of the rest of the function’s
external interface
. For example:

def foo(self):
    perform method operation 
foo = classmethod(foo) 

This becomes less readable with longer methods. It also seems less than pythonic to name
the function three times for what is conceptually a single
declaration
. A solution to this problem is to move the transformation
of the method closer to the method’s own declaration
. The intent of
the new syntax is to replace

def foo(cls):
    pass 
foo = synchronized(lock)(foo) 
foo = classmethod(foo)

with an alternative that places the decoration in the function’s declaration:

@classmethod
@synchronized(lock)
def foo(cls):
    pass
Answered By: Steven Rumbalski

A great example of why decorators are useful is the numba package.

It provides a decorator to speed up Python functions:

@jit
def my_slow_function():
    # ...

The @jit decorator does some very amazing and complex operations – it compiles the whole function (well, parts of it) to machine code. If Python did not have decorators you would have to write the function in machine code yourself… or more seriously, the syntax would look like this:

def my_slow_function():
    # ...

my_slow_function = jit(my_slow_function)

The purpose of the decorator syntax is to make the second example nicer. It’s just syntactic sugar so you don’t have to type the function name three times.

Answered By: MB-F

I will try to keep it simple and explain it with examples.
One of the things that I often do is measure the time taken by API’s that I build and then publish them on AWS.

Since it is a very common use case I have created a decorator for it.

def log_latency():
def actual_decorator(f):
    @wraps(f)
    def wrapped_f(*args, **kwargs):
        t0 = time()
        r = f(*args, **kwargs)
        t1 = time()
        async_daemon_execute(public_metric, t1 - t0, log_key)
        return r
    return wrapped_f

return actual_decorator

Now if there is any method that I want to measure the latency, I just put annotate it with the required decorator.

@log_latency()
def batch_job_api(param):
    pass

Suppose you want to write a secure API which only works if you send a header with a particular value then you can use a decorator for it.

def secure(f):
@wraps(f)
def wrapper(*args, **kw):
    try:
        token = request.headers.get("My_Secret_Token")
        if not token or token != "My_Secret_Text":
            raise AccessDenied("Required headers missing")
    return f(*args, **kw)
return wrapper

Now just write

@secure
def my_secure_api():
    pass

I have also been using the above syntax for API specific exceptions, and if a method needs a database interaction instead of acquiring a connection I use @session decorator which tells that this method will use a database connection and you don’t need to handle one yourself.

I could have obviously avoided it, by writing a function that checks for the header or prints time taken by API on AWS, but that just looks a bit ugly and nonintuitive.

There is no convention for this, at least I am not aware of them but it definitely makes the code more readable and easier to manage.
Most of the IDE’s also have different color syntax for annotation which makes it easier to understand and organize code.

So, I would it may just be because of inexperience; if you start using them you will automatically start knowing where to use them.

Answered By: hassan_ashraf

I came across a neatly-written article which explains decorators in Python, why we should leverage them, how to use them and more.

Some of the benefits of using decorators in Python include:

  1. Allows us to extend functions without actually modifying them.
  2. Helps us stick with the Open-Closed Principle.
  3. It helps reduce code duplication — we can factorize out repeated code into a decorator.
  4. Decorators help keep a project organized and easy to maintain.

Reference: https://tomisin.dev/blog/understanding-decorators-in-python

Answered By: Tomisin Abiodun
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.