Giter Club home page Giter Club logo

codetiming's Introduction

Python Timer Functions: Three Ways to Monitor Your Code

codetiming - A flexible, customizable timer for your Python code

Latest version Python versions Downloads Tests Checked with mypy Interrogate DocStrings Code style: black MIT license

Install codetiming from PyPI:

$ python -m pip install codetiming

The source code is available on GitHub.

For a complete tutorial on codetiming, see Python Timer Functions: Three Ways to Monitor Your Code on Real Python.

Basic Usage

You can use codetiming.Timer in several different ways:

  1. As a class:

    t = Timer(name="class")
    t.start()
    # Do something
    t.stop()
  2. As a context manager:

    with Timer(name="context manager"):
        # Do something
  3. As a decorator:

    @Timer(name="decorator")
    def stuff():
        # Do something

Arguments

Timer accepts the following arguments when it's created. All arguments are optional:

  • name: An optional name for your timer
  • text: The text that's shown when your timer ends. It should contain a {} placeholder that will be filled by the elapsed time in seconds (default: "Elapsed time: {:.4f} seconds")
  • initial_text: Show text when your timer starts. You may provide the string to be logged or True to show the default text "Timer {name} started" (default: False)
  • logger: A function/callable that takes a string argument and will report the elapsed time when the logger is stopped (default: print())

You can turn off explicit reporting of the elapsed time by setting logger=None.

In the template text, you can also use explicit attributes to refer to the name of the timer or log the elapsed time in milliseconds, seconds (the default), or minutes. For example:

t1 = Timer(name="NamedTimer", text="{name}: {minutes:.1f} minutes")
t2 = Timer(text="Elapsed time: {milliseconds:.0f} ms")

Note that the strings used by text are not f-strings. Instead, they are used as templates that will be populated using .format() behind the scenes. If you want to combine the text template with an f-string, you need to use double braces for the template values:

t = Timer(text=f"{__file__}: {{:.4f}}")

text is also allowed to be a callable like a function or a class. If text is a callable, it is expected to require one argument: the number of seconds elapsed. It should return a text string that will be logged using logger:

t = Timer(text=lambda secs: f"{secs / 86400:.0f} days")

This allows you to use third-party libraries like humanfriendly to do the text formatting:

from humanfriendly import format_timespan

t1 = Timer(text=format_timespan)
t2 = Timer(text=lambda secs: f"Elapsed time: {format_timespan(secs)}")

You may include a text that should be logged when the timer starts by setting initial_text:

t = Timer(initial_text="And so it begins ...")

You can also set initial_text=True to use a default initial text.

Capturing the Elapsed Time

When using Timer as a class, you can capture the elapsed time when calling .stop():

elapsed_time = t.stop()

You can also find the last measured elapsed time in the .last attribute. The following code will have the same effect as the previous example:

t.stop()
elapsed_time = t.last

Named Timers

Named timers are made available in the class dictionary Timer.timers. The elapsed time will accumulate if the same name or same timer is used several times. Consider the following example:

>>> import logging
>>> from codetiming import Timer

>>> t = Timer("example", text="Time spent: {:.2f}", logger=logging.warning)

>>> t.start()
>>> t.stop()
WARNING:root:Time spent: 3.58
3.5836678670002584

>>> with t:
...     _ = list(range(100_000_000))
... 
WARNING:root:Time spent: 1.73

>>> Timer.timers
{'example': 5.312697440000193}

The example shows how you can redirect the timer output to the logging module. Note that the elapsed time spent in the two different uses of t has been accumulated in Timer.timers.

You can also get simple statistics about your named timers. Continuing from the example above:

>>> Timer.timers.max("example")
3.5836678670002584

>>> Timer.timers.mean("example")
2.6563487200000964

>>> Timer.timers.stdev("example")
1.311427314335879

timers support .count(), .total(), .min(), .max(), .mean(), .median(), and .stdev().

Acknowledgments

codetiming is based on a similar module initially developed for the Midgard Geodesy library at the Norwegian Mapping Authority.

codetiming's People

Contributors

alkatar21 avatar dbader avatar gahjelle avatar gahjelle-nb avatar janfreyberg avatar pricemg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

codetiming's Issues

Instantiate and start timer on single line

It would be helpful if there was a way to start the timer when it is instantiated, such as t = Timer().start() or t = Timer(now=True).

This can reduce the number of required lines from 3 to 2 which is nice when inserting many timers.

t = Timer().start()
# code block 1
t.stop()

t = Timer().start()
# code block 2
t.stop()

t = Timer().start()
# code block 3
t.stop()

To do this, either .start() would need to return self or a Timer.__post_init__ function would need to if self.now == True.

Add CHANGELOG.md

Version 1 is synced to the published article. As development happens on the package, we want to have an easy to reference overview of changes made.

Add CONTRIBUTING.md and AUTHORS.md

We should have guidelines for people who want to contribute. And more importantly, make sure they get credit for their contributions.

Timer class as defined here does not implement the decorator protocol

Your article's last step is to implement the call() method to create a decorator for Timer, but that code isn't included here. The article shows passing a text="string" to the decorator, but that does not appear to work as shown (the output in the article shows the display text changing as specified, but the code as entered from the article still displays the default text).

I was wondering if it was an oversight not including the call() method here, or if a fully functional version could be provided.

Mark package as typed with `py.typed` (PEP 561)

Since this package is marked as typed on PyPI and the code appears to be typed at first glance, it would make sense to mark this module as typed according to PEP 561.

Currently, for example, Pylance(pyright) indicates that this is missing:
grafik

Add a simple way of resetting the accumulated timers

Today, it's possible to reset the accumulated timers by clearing the timers dictionary:

Timer.timers.clear()

However, this may lead to errors if a timer has already been initialized (typically because it's used as a decorator and initialized at import-time). Therefore a better way is to explicitly set existing timers to 0:

Timer.timers.update({k: 0 for k in Timer.timers})

However, this becomes hard to read, and even harder to write.

A better solution would be to have explicit support for resetting the accumulated timers.

Unneccessary use of f-strings

f-strings are used in the TimerError messages. However, no formatting is done, so these should just be regular string literals.

Add public API for type checkers

The code works like this, but at least pylance can recognize that there is Timer, but does not recognize it as part of the public API:

grafik

Adding the following to the __init__.py would solve this.

# Use __all__ to let type checkers know what is part of the public API.
__all__ = ('Timer', 'TimerError')

I could make a PR for this. But then the question would arise whether __all__ should also be added in the modules and whether relative imports should be used in general?

Timer name in printed message

It would be good to be able to have the timer name in the printed message. Either by default or have some special parameter or special formatting parameter. At the moment printouts from multiple timers can be confusing.

codetiming==1.1.0 from pypi

Include {name} in default format string

A small quality-of-life improvement would be if the default format string included the name attribute by default.

>>> t = Timer('foobar')
>>> t.start()
>>> time.sleep(1)
>>> t.stop()
foobar elapsed time: 1.000 seconds
>>> t = Timer()
>>> t.start()
>>> time.sleep(1)
>>> t.stop()
Elapsed time: 1.000 seconds

This might be achieved with the following __post_init__ function:

    def __post_init__(self):
        if self.name is None:
            self.text = "Elapsed time: {:0.4f} seconds"
        else:
            self.text = "{name} elapsed time {:0.4f} seconds"

Alternatively, name could just have a non-None default value (e.g. "Timer" or "Elapsed").

Error when setting a Timer text formatted as a json

I would like to log the elapsed time with the function's parameters as a json.

If I run the code below:

import time
from codetiming import Timer
import sys

def get_text(requestId, funcContext, parameters_to_exclude):
	_data = {
		'requestId': requestId,
		'exec_time_ms': '{:.0f}',
		'exec': {k:v for k,v in funcContext.f_locals.items() if k not in parameters_to_exclude}
	}
	return '{}'.format(_data)

def principal(name, age, city):
    
    print(get_text('111', sys._getframe(), []))

    with Timer():

        time.sleep(2)

principal(name='lara', age=30, city='boston')

I will get the following output:

{'requestId': '111', 'exec_time_ms': '{:.0f}', 'exec': {'name': 'lara', 'age': 30, 'city': 'boston'}}
Elapsed time: 2.0022 seconds

But I would like to get the following one:

{'requestId': '111', 'exec_time_ms': 2002, 'exec': {'name': 'lara', 'age': 30, 'city': 'boston'}}

Then I changed from Timer() to:

with Timer(text=get_text('111', sys._getframe(), ['request', 'info'])):

But after the change, I'm getting the following error:

Traceback (most recent call last):
  File "/usr/lib/python3.7/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/lib/python3.7/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/home/kleyson/.vscode/extensions/ms-python.python-2021.9.1191016588/pythonFiles/lib/python/debugpy/__main__.py", line 45, in <module>
    cli.main()
  File "/home/kleyson/.vscode/extensions/ms-python.python-2021.9.1191016588/pythonFiles/lib/python/debugpy/../debugpy/server/cli.py", line 444, in main
    run()
  File "/home/kleyson/.vscode/extensions/ms-python.python-2021.9.1191016588/pythonFiles/lib/python/debugpy/../debugpy/server/cli.py", line 285, in run_file
    runpy.run_path(target_as_str, run_name=compat.force_str("__main__"))
  File "/usr/lib/python3.7/runpy.py", line 263, in run_path
    pkg_name=pkg_name, script_name=fname)
  File "/usr/lib/python3.7/runpy.py", line 96, in _run_module_code
    mod_name, mod_spec, pkg_name, script_name)
  File "/usr/lib/python3.7/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/data/detran/dev/face-recognition/software/app/test.py", line 24, in <module>
    principal(name='lara', age=30, city='boston')
  File "/data/detran/dev/face-recognition/software/app/test.py", line 21, in principal
    time.sleep(2)
  File "/home/kleyson/.virtualenvs/face-recognition/lib/python3.7/site-packages/codetiming/_timer.py", line 74, in __exit__
    self.stop()
  File "/home/kleyson/.virtualenvs/face-recognition/lib/python3.7/site-packages/codetiming/_timer.py", line 60, in stop
    text = self.text.format(self.last, **attributes)
KeyError: "'requestId'"

How to do that ? Is that possible ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.