Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python wrapper function taking arguments inside decorator

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

like image 818
codyc4321 Avatar asked Jun 18 '15 00:06

codyc4321


2 Answers

  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.

like image 126
mVChr Avatar answered Sep 21 '22 20:09

mVChr


You need to add the arg to wrapper and then some_function:

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

If you want to use the decorator on different functions use *args and ** kwargs which will also work for functions that take no args:

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

On a side note you may find the timeit module useful if you want to time your code, if you have ipython installed you can simply use timeit your_function.

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

No, A decorator in Python is a callable Python object that is used to modify a function, method or class definition. The original object, the one which is going to be modified, is passed to a decorator as an argument. The decorator returns a modified object, e.g. a modified function, which is bound to the name used in the definition, there are more examples here

like image 30
Padraic Cunningham Avatar answered Sep 17 '22 20:09

Padraic Cunningham