python wrapper function taking arguments inside decorator

15,455

Solution 1

  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.

Solution 2

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

Share:
15,455
codyc4321
Author by

codyc4321

Updated on June 04, 2022

Comments

  • codyc4321
    codyc4321 about 2 years

    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

  • krethika
    krethika about 9 years
    although the OP used a single arg, this seems like a generic timing function so that def wrapper(*args, **kwargs) might be a more appropriate signature.
  • Padraic Cunningham
    Padraic Cunningham about 9 years
    @mehtunguh, yes, I was just using the OP's code as an example. My net went down so I could not edit to add a generic example.
  • codyc4321
    codyc4321 about 9 years
    How would def wrapper(*args, **kwargs) get the args it needs if timing_function takes (some_function) and not (some_function, *args, **kwargs)
  • mVChr
    mVChr about 9 years
    Because the decorator is essentially doing this: my_function = timing_function(my_function) so even though it looks like you're calling my_function you're actually calling wrapper since timing_function returns wrapper. So the new my_function is wrapper and so wrapper receives the args and kwargs you pass to my_function.
  • codyc4321
    codyc4321 about 9 years
    #2 was more important to understand, ty all
  • AbdurRehman Khan
    AbdurRehman Khan about 4 years
    Damn, thank you @mVChr, I've been trying to understand where wrapper gets its args and kwargs from and your comment blew my mind!