Skip to content

Instantly share code, notes, and snippets.

@gwatts
Last active August 11, 2017 05:09
Show Gist options
  • Save gwatts/a70bd01a1459a9eca3e8df76320a8430 to your computer and use it in GitHub Desktop.
Save gwatts/a70bd01a1459a9eca3e8df76320a8430 to your computer and use it in GitHub Desktop.
Simple code timer for Python. Requires Python 3.2+
# The MIT License (MIT)
#
# Copyright (c) 2017 Gareth Watts
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
'''A simple wall-clock code timer.'''
from functools import wraps
import time
import threading
class timeit(dict):
'''Report time taken for a piece of code to execute.
This can be used either as a context manager, or as a decorator.
Examples:
@timeit('mytimer')
def timethis():
time.sleep(1)
def myfunc():
with timeit('mytimer'):
time.sleep(1)
The string output by timeit can be changed by supplying a fmt keyword
to __init__. Additionally, the object returned by the context manager can be
treated as a dictionary, and entries assigned to it can be referenced in the
format string, eg. to include result information:
with timeit('make_request', fmt=timeit.fmt+' result={result}') as timer:
result = make_http_call('http://example.com')
timer['result'] = result
# outputs something like:
# timeit name='make_request' id=2 runtime=0.1s result=42
Each invocation of the timer generates a new id, higher than the last. There
may be gaps in numbering. it is thread safe.
Args:
name (str): Name to assign to this timer. Displayed in formatted output
fmt (str, optional): Override the default format string
'''
__id__ = 0
__lck__ = threading.Lock()
__cache__ = threading.local()
#: string: Default format string used to print timer output
fmt = "timeit name={name!r} id={id} runtime={runtime:.2}s"
@classmethod
def __next_id__(cls):
with cls.__lck__:
cls.__id__ += 1
return cls.__id__
def __init__(self, name, fmt=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self._start = None
self.id = self.__next_id__()
self.name = name
if fmt:
self.fmt = fmt
ins = getattr(self.__cache__, name, None)
if ins is None:
setattr(self.__cache__, name, [])
def __getitem__(self, key):
if key == "id":
return self.id
if key == 'name':
return self.name
return super().__getitem__(key)
def __enter__(self):
getattr(self.__cache__, self.name).append(self)
self._start = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self['runtime'] = time.time() - self._start
getattr(self.__cache__, self.name).pop()
self.emit(self.fmt.format_map(self))
def __call__(self, fn):
@wraps(fn)
def wrapper(*args, **kwargs):
with type(self)(self.name, self.fmt, self) as t:
return fn(*args, **kwargs)
return wrapper
def emit(self, msg):
'''Emits the timer message; subclass and override to direct output elsewhere.'''
print(msg)
@classmethod
def byname(cls, name):
'''Byname returns the innermost timeit active instance with the given name.'''
return getattr(cls.__cache__, name)[-1]
@classmethod
def set(cls, name, **kwargs):
'''Set updates the innermost named timeout instance with the supplied values.
This is useful when used with a timeit decorated function to pass
data to format variables as it behaves correctly with recursive functions.
'''
cls.byname(name).update(kwargs)
if __name__ == '__main__':
# Use it as a decorator
@timeit('dectest')
def go(arg1):
print('running...', arg1)
time.sleep(0.2)
go("one")
go("two")
# Or as a context manager, with optional format override
with timeit("withtest", fmt="Test {id} took {runtime} seconds"):
print("running...")
time.sleep(0.3)
# or add your own information to it to print
with timeit('another test', fmt=timeit.fmt+' result={result}') as t:
time.sleep(0.15)
t['result'] = "42"
# supply custom args to the decorator for use in the fmt string,
# use it recurisvely and update each recursive instance with values specific
# to the call
@timeit('factorial', fmt='factorial n={n} returned result={result} after {runtime}')
def factorial(n):
time.sleep(0.1 * n)
if n > 1:
result = n * factorial(n-1)
else:
result = 1
timeit.set('factorial', n=n, result=result)
return result
print(factorial(5))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment