2

I am trying to access the time to execute a function that is inside a decorator function.

I followed the direction in this post because I was unable to get the function to take the arguments I passed to it. Now I am unsure of how to get the data from calling the function.

Here is my code:

import time
from functools import wraps

def sort_timer(func):
    def outer(func):
        @wraps(func)
        def inner(*args, **kwargs):
            start = time.perf_counter()
            func(*args, **kwargs)
            finish = time.perf_counter()
            return start - finish
        return inner
    return outer

@sort_timer
def bubble_sort(a_list):
    """Sorts a_list in ascending order"""
    for pass_num in range(len(a_list) - 1):
        for index in range(len(a_list) - 1 - pass_num):
            if a_list[index] > a_list[index + 1]:
                temp = a_list[index]
                a_list[index] = a_list[index + 1]
                a_list[index + 1] = temp

list1 = [60, 19, 22, 14, 43, 27, 3, 77]
x = bubble_sort(list1)
print(x)

It would appear that what is being returned is the inner function. Here is what is logged to the console:

<function sort_timer.<locals>.outer.<locals>.inner at 0x0000027167770310>

Any insight would be appreciated. Thank you.

TypeError
  • 31
  • 5

3 Answers3

2

The code in the answer to which you refer is specifically for supplying arguments to the decorator. It will work as long as you put () after @sort_timer... and, as currently written, provide an argument. Here's an example, with the first func parameter renamed for clarity:

def sort_timer(parameter):
    def outer(func):
        @wraps(func)
        def inner(*args, **kwargs):
            start = time.perf_counter()
            func(*args, **kwargs)
            finish = time.perf_counter()
            return start - finish
        return inner
    return outer

@sort_timer(13) # for example
...

That said, in your code as shown, you're not really using the double-wrapper technique to any advantage. Unless you plan on supplying arguments to the decorator itself (not the decorated function), you could simplify like so:

def sort_timer(func):
    @wraps(func)
    def inner(*args, **kwargs):
        start = time.perf_counter()
        func(*args, **kwargs)
        finish = time.perf_counter()
        return start - finish
    return inner
CrazyChucky
  • 3,263
  • 4
  • 11
  • 25
  • Okay. I think I understand. So, if I were to use that technique I can supply arguments to the decorator function itself to use in the outer function. – TypeError Jul 20 '22 at 04:11
  • 1
    Yes, exactly. You would supply them to the decorator, so instead of `@sort_timer`, you could do something like `@sort_timer('seconds')` (just as an example). – CrazyChucky Jul 20 '22 at 04:13
  • 1
    @CrazyChucky Linked answer is correct, it's just that the intended usage is for the decorator be passed arguments. Even though there aren't any in this case, `@sort_timer()` can be used. You can see that the example decorator has arguments provided in the linked answer. – SuperStormer Jul 20 '22 at 06:01
  • @SuperStormer Thank you! I messed up and completely forgot how the semantics of defining argument-accepting decorators work; I had it in my head that the decorated function was just one of the arguments it accepted. Fixed. – CrazyChucky Jul 20 '22 at 07:05
1

Why not using a simple decorator for timing like e.g. mentioned in timeit versus timing decorator ?

import time
import functools

def timeit(f):
    @functools.wraps(f)
    def timed(*args, **kw):

        ts = time.time()
        result = f(*args, **kw)
        te = time.time()

        print(f"func:{f.__name__} args:{args}{'' if len(kw) == 0 else kw} took: {(te-ts)*1000 :.3f} msec")
        return result

    return timed

@timeit
def bubble_sort(a_list):
    """Sorts a_list in ascending order"""
    for pass_num in range(len(a_list) - 1):
        for index in range(len(a_list) - 1 - pass_num):
            if a_list[index] > a_list[index + 1]:
                temp = a_list[index]
                a_list[index] = a_list[index + 1]
                a_list[index + 1] = temp

list1 = [60, 19, 22, 14, 43, 27, 3, 77]
x = bubble_sort(list1)

which prints:

func:bubble_sort args:([3, 14, 19, 22, 27, 43, 60, 77],) took: 0.029 msec

The @functools.wraps(f) takes f's meta information and copies it to the decorating function timed, so that the resulting decorated function keeps the features of the original/target function.

Gwang-Jin Kim
  • 9,303
  • 17
  • 30
  • Hello Gwang-Jin Kim. This is the implementation this is the implementation that I wound up using. I am very new to decorators, and I was a little unsure of how to implement them. So, I was looking for how to use the decorators. That's why the version I put in my question was more complicated. – TypeError Jul 20 '22 at 06:05
  • 1
    @TypeError A Decorator in python is nothing than a function which takes another function and uses/calls it in a new function it defines inside its definition - and the newly defined function - which uses/calls the other function - is returned at the end of the decorator definition. So it defines a function which in turn calls the target function. But before or after it calls the other function, you can add a myriad of other stuff it does in addition besides calling the other function. So the decorator adds additional functionality to a simple function call. Thus it 'decorates'. – Gwang-Jin Kim Jul 20 '22 at 08:17
  • @TypeError I was looking about the `@functools.wraps` decorator - it copies some metainformation of the decorated function to the decorating function. So I will add it to the decorator. – Gwang-Jin Kim Jul 20 '22 at 08:22
-1

Here is the code for calculating execute time of a function:

import time
 
def timer(func):
  def wrapper(*args, **kwargs):
    start = time.time()
    func(*args, **kwargs)
    end = time.time() - start
    print(end)
  return wrapper
Khanzadeh_AH
  • 139
  • 6