16
\$\begingroup\$

This decorator adds the elapsed time to a function's attributes when applied.

My concerns:

  • Is the code pythonic?
  • Could this code be useful?
  • Should I just use the timeit module?
  • Is the code easy to read and understand?

The code:

'''
:Date: 7/21/17
:Version: 1
:Authors:
 - Ricky L Wilson
'''
 
import datetime
def time_func(function):
 """ This decorator calculates the amount of time a function takes to execute.
 
 When time_func is applied to a function it records how long the function takes to
 finish and add the elapsed time to the functions attributes.
 
 - **parameters**
 :param function: The function you want to add the elapsed time attribute to.
 
 :Example:
 @time_func 
 def example(name, **kwargs):
 meta = type(name, (object,), kwargs)
 return meta
 
 example('foo')
 print example.elapsed
 0:00:00.000052
 
 """
 def new_func(*args, **kwargs):
 # Start the clock.
 start = datetime.datetime.now()
 # Execute the function and record the results.
 function_result = function(*args, **kwargs)
 # Calculate the elapsed time and add it to the function
 # attributes.
 new_func.elapsed = datetime.datetime.now() - start
 # Returned the function with the added elapsed attribute 
 return function_result
 return new_func
 
asked Jul 21, 2017 at 18:24
\$\endgroup\$
1
  • 2
    \$\begingroup\$ ISO 8601 for the time stamp: 2017年07月21日. \$\endgroup\$ Commented Jul 22, 2017 at 1:38

1 Answer 1

23
\$\begingroup\$

I'd recommend the timeit module when measuring the execution time of functions. AFAIK, timeit disables the garbage collector for the duration of the test, which might give you better results overall.

From here:

timeit is more accurate, for three reasons:

  • it repeats the tests many times to eliminate the influence of other tasks on your machine, such as disk flushing and OS scheduling.
  • it disables the garbage collector to prevent that process from skewing the results by scheduling a collection run at an inopportune moment.
  • it picks the most accurate timer for your OS, time.time or time.clock, see timeit.default_timer.

On the other side, a timing decorator is really useful because you can use annotations to sprinkle the timing around your code rather than making your code messy with timing logic everywhere. So yes, related to one of your questions, the code is useful.

Now, on the pythonic question, IMO:

  • you have too many comments which unfortunately didn't add any value to your code. Remove them.
  • your inner function could also be renamed to something more intuitive like wrapper.
  • function is also not the best choice when it comes to naming conventions as it might shadow the built-in function
  • use 2 newlines between imports and methods
  • the indentation should be a multiple of 4 (spaces)
  • use triple double quotes for your module docstring

In Python, there're already some useful modules to help you with this. For example, in functools you have the wraps decorator. This takes a function used in a decorator and adds the functionality of copying over the function name, docstring, arguments list, etc. And since wraps is itself a decorator I guess it'll make things a lot easier.


Code:

from functools import wraps
from time import time
def timing(f):
 @wraps(f)
 def wrapper(*args, **kwargs):
 start = time()
 result = f(*args, **kwargs)
 end = time()
 print 'Elapsed time: {}'.format(end-start)
 return result
 return wrapper

Usage:

@timing
def f(a):
 for _ in range(a):
 pass
print(f(2000000))

Result:

Elapsed time: 0.0971460342407

answered Jul 21, 2017 at 19:22
\$\endgroup\$
5
  • \$\begingroup\$ IMO it'd be good to be able to enable the GC. Also I think enabling the GC should be the default setting. \$\endgroup\$ Commented Jul 21, 2017 at 19:53
  • 2
    \$\begingroup\$ Well, timeit disables the GC to prevent \$X\$ process from skewing the results by scheduling a collection run at an inopportune moment. \$\endgroup\$ Commented Jul 21, 2017 at 19:57
  • \$\begingroup\$ Hardly seems helpful trying to isolate the time of f to have to add the non-idempotent timing of GC. \$\endgroup\$ Commented Jul 21, 2017 at 21:41
  • 4
    \$\begingroup\$ @ShawnMehan If you want to know the raw execution time of a function, ya, disabling GC may skew the time a bit. If you want to compare the execution times of different versions of a function however (which to me seems like the more likely case), eliminating as many of the sporadic factors as possible should make comparisons more accurate. You want to compate the difference between times, not the total possible duration of each call. \$\endgroup\$ Commented Jul 21, 2017 at 22:35
  • \$\begingroup\$ Can add the function name to print statement so that it prints the name of the function as well along with the execution time \$\endgroup\$ Commented Jun 3, 2021 at 15:25

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.