python wrapper function taking arguments inside decorator

codyc4321 picture codyc4321 · Jun 18, 2015 · Viewed 10.6k times · Source

I am trying to write python decorators and I am having problems understanding how the internal wrapper takes arguments. I have here:

import time

def timing_function(some_function):
    def wrapper():
        t1 = time.time()
        some_function()
        t2 = time.time()
        return "Time it took to run: " + str((t2-t1)) + "\n"
    return wrapper

@timing_function
def my_function(x):
    return x * x

my_function(6)

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-fe2786a2753c> in <module>()
----> 1 my_function(6)

TypeError: wrapper() takes no arguments (1 given)

Which is slightly different than the example:

import time

def timing_function(some_function):

    """
    Outputs the time a function takes
    to execute.
    """

    def wrapper():
        t1 = time.time()
        some_function()
        t2 = time.time()
        return "Time it took to run the function: " + str((t2-t1)) + "\n"
    return wrapper

@timing_function
def my_function():
    num_list = []
    for x in (range(0,10000)):
        num_list.append(x)
    return "\nSum of all the numbers: " +str((sum(num_list)))


print my_function()

Time it took to run the function: 0.0

It seems the problem is the 'x' argument. I tried giving wrapper *args, but it also did not work. My questions are

  1. What is the proper way to allow arguments in this simple wrapper? Thank you

  2. Why do all decorator examples I've seen always have an internal function, can you not write a decorator as one function?

Thank you

Answer

mVChr picture mVChr · Jun 18, 2015
  1. What is the proper way to allow arguments in this simple wrapper? Thank you

You need to pass the argument from my_function to wrapper, i.e.:

def wrapper(x):

If you want it to be able to handle many more functions generically then you'd have to do something like:

def wrapper(*args, **kwargs):

But then your logic within the decorator would also need to be able to handle the args and kwargs generically.

  1. Why do all decorator examples I've seen always have an internal function, can you not write a decorator as one function?

Because a decorator is a function that takes a function as an argument and returns a function that is executed as a wrapper of the original function. In fact, decorators are often written as three functions:

from functools import wraps

def somedec(somearg, someopt=None):
    def somedec_outer(fn):
        @wraps(fn)
        def somedec_inner(*args, **kwargs):
            # do stuff with somearg, someopt, args and kwargs
            response = fn(*args, **kwargs)
            return response
        return somedec_inner
    return somedec_outer

Why do this? You can pass some information to the decorator based on the type of function you're decorating, or maybe different ways the decorator behaves.

@somedec(30.0, 'foobarbaz')
def somefn(a, b, c):
    return a + b + c

@somedec(15.0, 'quxjinzop')
def otherfn(d, e, f):
    return d - e - f

functools.wraps will make the decorated function look like the original function to the Python interpreter. This is helpful for logging and debugging and such and is a best practice to use when creating decorators.