Artem Andreev
Artem Andreev

Reputation: 19922

Different implementations of decorators in python

Sorry for long question, but I don't know how to make it shorter.

1. Decorators without args

Implementation 1.1 (through function)

import time
import functools

def pause(f):
    @functools.wraps(f)
    def wrapper(*args, **kwargs):
        time.sleep(1)
        return f(*args, **kwargs)

    return wrapper

@pause
def func(x, y):
    """desc"""
    return x + y

print func(1, 2)
help(func)

Output:

3
Help on function func in module __main__:

func(*args, **kwargs)
    desc

It's "classical" implementation - a lot of articles contain it.

-breaks function signature (can be solved by nonstandard module decorator)

-you must use functools for saving name and doc string of function (not very big problem, but slightly more code and magic)

+you can modify function args (not always necessary)

+-? Anything else?

Implementation 1.2 (through function)

import time

def pause(f):
    time.sleep(1)
    return f

@pause
def func(x, y):
    return x + y

print func(1, 2)
help(func)

Output:

3
Help on function func in module __main__:

func(x, y)
    desc

? Why it implementation rarely used in articles and examples? What have I missed?

-you can't modify function args

+not breaks function signature

+less code

+-? Anything else?

Implementation 1.3 (through classes)

import time

class pause(object):
    def __init__(self, f):
        self.f = f
    
    def __call__(self, *args, **kwargs):
        time.sleep(1)
        return self.f(*args, **kwargs)

@pause
def func(x, y):
    """desc"""
    return x + y

print func(1, 2)
help(func)

Output:

3
Help on pause in module __main__ object:

class pause(__builtin__.object)
 |  Methods defined here:
 |  
 |  __call__(self, *args, **kwargs)
 |  
 |  __init__(self, f)
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

IMHO, ugly.

Implementation 1.4 (through classes)

import time

class pause(object):
    def __call__(self, f):
        time.sleep(1)
        return f

@pause()
def func(x, y):
    """desc"""
    return x + y

print func(1, 2)
help(func)

Output:

3
Help on function func in module __main__:

func(x, y)
    desc

-you can't modify function args

-nonstandard syntax for decorator use

+not breaks function signature

+-less code (but more than implementation 2 :) )

+-? Anything else?

2. Decorators with args

Implementation 2.1 (through function)

import time
import functools

def pause(t):
    def wrapper(f):
        @functools.wraps(f)
        def tmp(*args, **kwargs):
            time.sleep(t)
            return f(*args, **kwargs)
        return tmp

    return wrapper

@pause(1)
def func(x, y):
    """desc"""
    return x + y

print func(1, 2)
help(func)

Output:

3
Help on function func in module __main__:

func(*args, **kwargs)
    desc

It's "classical" implementation - a lot of articles contain it.

-breaks function signature

-nested is worse than flat

-you must use functools for saving name and doc string of function

+you can modify function args

+-? Anything else?

Implementation 2.2 (through function)

import time

def pause(t):
    def wrapper(f):
        time.sleep(t)
        return f
    return wrapper

@pause(1)
def func(x, y):
    """desc"""
    return x + y

print func(1, 2)
help(func)

Output:

3
Help on function func in module __main__:

func(x, y)
    desc

? Why it implementation rarely used in articles and examples? What have I missed?

-you can't modify function args

+not breaks function signature

+less code

+-? Anything else?

Implementation 2.3 (through classes)

import time

class pause(object):
    def __init__(self, darg):
        self.darg = darg
    
    def __call__(self, f):
        time.sleep(self.darg)
        return f

@pause(1)
def func(x, y):
    """desc"""
    return x + y

print func(1, 2)
help(func)

Output:

3
Help on function func in module __main__:

func(x, y)
    desc

-you can't modify function args

+not breaks function signature

+-less code (but more than implementation 2 :) )

IMHO+ little clearer than nested functions

+-? Anything else?

Questions in one place

  1. Why implementations 1.1/2.1 are more common in articles and examples as compared with implementations 1.2/2.2?
  2. What are the pros and cons I missed?
  3. Did I missed something else?

Upvotes: 2

Views: 322

Answers (1)

Gintautas Miliauskas
Gintautas Miliauskas

Reputation: 7892

It sure is good that the implementations 1.2 & 2.2 are "less common than" 1.1 and 2.1, because they do different things. In fact, I wouldn't call them "decorators", because they don't really wrap ("decorate") the function. Instead, they only perform their action once, at the moment the decorated function is parsed. That is, their action (sleep in your case) is performed at function definition time, not at function invocation time.

Try invoking your decorated functions several times and you will see the difference.

Upvotes: 2

Related Questions