简体   繁体   中英

Using decorators with and without arguments simultaneously

I'm trying to measure the run-time of a decorated function like this:

import time

def timing_function(some_function):
    """Outputs the time a function takes to execute."""
    def wrapper():
        t1 = time.time()
        some_function()
        time.sleep(1)
        t2 = time.time()
        return "Time it took to run the function " + some_function.__name__ + " is " + str((t2-t1)) + "\n"
    return wrapper

def tags(tag_name):
    def tags_decorator(my_func):
        """Adds tags to a string."""
        def func_wrapper(name):
            return "<{0}>{1}</{0}>".format(tag_name, my_func(name))
        return func_wrapper
    return tags_decorator

@timing_function
@tags("p")
def get_text(name):
    return "Hello "+name

Then I try

print get_text("World")

but get

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

Interestingly, I added a piece of code to check what are the arguments I'm giving to wrapper():

def wrapper(*args):
    print "args", args
    # ...

It seems to get the argument "World", which should be actually passed to get_text .

Inverting the order of the decorators doesn't help. Is there anything I could do to implement in general decorators with and without arguments at the same time?

Of course I can merge both codes into a single wrapper, but that's not what I'm looking for...

I'm using python 2.6

You need to adjust your wrapper() function to accept an arbitrary number of arguments (positional and keyword), and pass those on to the wrapped function:

def timing_function(some_function):
    """Outputs the time a function takes to execute."""
    def wrapper(*args, **kw):
        t1 = time.time()
        some_function(*args, **kw)
        time.sleep(1)
        t2 = time.time()
        return "Time it took to run the function " + some_function.__name__ + " is " + str((t2-t1)) + "\n"
    return wrapper

The *args and **kw syntax in the wrapper function signature capture any positional and keyword arguments in a tuple and a dictionary, respectively. The very similar (related) syntax in a call expression takes a sequence or a dictionary and applies the contents of those as positional or keyword arguments to the object being called. This neatly passes on any number of arguments from the wrapper to the wrapped.

This wasn't an issue with your tags decorator, as its wrapper takes the exact same arguments as the wrapped function. That limits that decorator to functions that take exactly one argument (which is fine too).

Take into account that this decorator ignores the return value of the wrapped function; you are instead returning the timing results. You may want to still make the return value accessible (perhaps by returning a tuple of (return_value, timing_info) ).

Demo:

>>> import time
>>> def timing_function(some_function):
...     """Outputs the time a function takes to execute."""
...     def wrapper(*args, **kw):
...         t1 = time.time()
...         some_function(*args, **kw)
...         time.sleep(1)
...         t2 = time.time()
...         return "Time it took to run the function " + some_function.__name__ + " is " + str((t2-t1)) + "\n"
...     return wrapper
... 
>>> def tags(tag_name):
...     def tags_decorator(my_func):
...         """Adds tags to a string."""
...         def func_wrapper(name):
...             return "<{0}>{1}</{0}>".format(tag_name, my_func(name))
...         return func_wrapper
...     return tags_decorator
... 
>>> @timing_function
... @tags("p")
... def get_text(name):
...     return "Hello "+name
... 
>>> get_text('World')
'Time it took to run the function func_wrapper is 1.00514888763\n'

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM