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
1 Answer 1
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
ortime.clock
, seetimeit.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-infunction
- 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
-
\$\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\$2017年07月21日 19:53:57 +00:00Commented 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\$Grajdeanu Alex– Grajdeanu Alex2017年07月21日 19:57:14 +00:00Commented 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 ofGC
. \$\endgroup\$Shawn Mehan– Shawn Mehan2017年07月21日 21:41:00 +00:00Commented 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\$Carcigenicate– Carcigenicate2017年07月21日 22:35:28 +00:00Commented 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\$ThePyGuy– ThePyGuy2021年06月03日 15:25:33 +00:00Commented Jun 3, 2021 at 15:25
Explore related questions
See similar questions with these tags.
2017年07月21日
. \$\endgroup\$