add a time decorator to timeit.py

I think that a time decorator will be a useful addition to the sandard library, as i find the current way of measuring execution time a bit tedious: timeit.timeit("fun_to_time(a, b)", setup="from __main__ import a, b", number=1) compared to: @timef def fun_to_time(a, b): ... or timef(print)("Hello world!"). I already made a basic implementation of it, and it's working well.

On Sun, Oct 07, 2018 at 10:16:08AM +0000, Amjad Ben Hedhili wrote:
There are lots of ways to call timeit, but generally if you are calling it with number=1 then your results may not be trustworthy. (That depends on the function, of course.) There's a reason why timeit defaults to number=1000000 rather than 1.
The problem with that as an API is that once you have decorated the function to call timeit, how do you use it *without* calling timeit? If you use functools.wraps, there will be a func_to_time.__wrapped__ but I don't think that calling that directly is a good interface.
Can you explain what the timef decorator does? What arguments does it take, how do we use it? -- Steve

this is my implementation: ``` units = {"auto": -1, "s": 1, "ms": 1e3, "μs": 1e6, "ns": 1e9} def timef(unit="auto", number=1, garcol=True, get_time=False): """A decorator to measure the execution time of a function Returns the return of the tested function if get_time is False or the execution time in unit if not unit: one of "ns" (nanoseconds), "μs" (microseconds), "ms" (milliseconds), "s" (seconds) and "auto": (automatic) (default: "auto") garcol: if True enables garbage collection (default: True) get_time: if true return the execution time of the function (default: False) """ fun = False if callable(unit): fun = True func = unit unit = "auto" elif unit not in units: raise ValueError( "valid options: " "s(seconds), " "ms(milliseconds), " "μs(microseconds), " "ns(nanoseconds) " "and auto(automatic: default)") def decorating_function(func): return _wrapper(func, number, units, unit, units[unit], garcol, get_time) return decorating_function(func) if fun else decorating_function def _wrapper(func, number, units, unit, factor, garcol, get_time): def timed(*args, **kwargs): nonlocal unit, factor gcold = gc.isenabled() # disable garbage collection on demand if not garcol: gc.disable() try: if number > 0: start = default_timer() for _ in range(number): func(*args, **kwargs) time = default_timer() - start result = func(*args, **kwargs) finally: if gcold: gc.enable() if unit == "auto": for u, f in units.items(): if 1 <= int(time * f) < 1000: unit, factor = u, f break else: unit, factor = "s", 1 time *= factor print(f"{func.__qualname__}: {time:.4f} {unit}") return time if get_time else result return timed ``` it's not perfect but it works; you can control number of repetitions, garbage collection, unit ... and it produces a formatted output such as: `fun_to_time: 24.1056 ms`. ________________________________ De : Python-ideas <python-ideas-bounces+amjadhedhili=outlook.com@python.org> de la part de Steven D'Aprano <steve@pearwood.info> Envoyé : dimanche 7 octobre 2018 04:15 À : python-ideas@python.org Objet : Re: [Python-ideas] add a time decorator to timeit.py On Sun, Oct 07, 2018 at 10:16:08AM +0000, Amjad Ben Hedhili wrote:
There are lots of ways to call timeit, but generally if you are calling it with number=1 then your results may not be trustworthy. (That depends on the function, of course.) There's a reason why timeit defaults to number=1000000 rather than 1.
The problem with that as an API is that once you have decorated the function to call timeit, how do you use it *without* calling timeit? If you use functools.wraps, there will be a func_to_time.__wrapped__ but I don't think that calling that directly is a good interface.
Can you explain what the timef decorator does? What arguments does it take, how do we use it? -- Steve _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/

On Sun, Oct 07, 2018 at 11:43:40AM +0000, Amjad Ben Hedhili wrote:
this is my implementation:
I didn't ask about the current implementation, as that might change. I asked about the interface: what it does, not how it does it. For the purposes of Python-Ideas, we want to discuss the feature, not the details of how it works, why it is better than other existing solutions, and whether or not it belongs in the std lib. You're selling the *idea*, not the *implementation* (although of course you can use the implementation to demonstrate the idea). Unfortunately, not every good idea can or will make it into the std lib. Even if the idea is good, we might decide that it belongs as a third- party library, or that the benefit is not enough to make up for the cost in effort or complexity. Or the idea is simply too controversial. -- Steve

On Sun, Oct 7, 2018 at 11:34 PM Steven D'Aprano <steve@pearwood.info> wrote:
Or just too detaily. The given function has four keyword arguments that vary its behaviour slightly; if it gets into the stdlib, someone will ask for a way to control the output format, someone else will request that it be able to send to stderr instead of stdout, etc, etc, and suddenly the proposed function has two dozen kwargs. One of the powerful tools available to a programmer is having *your own* debug library, without having to worry about what other people would do that might be similar. -0.5 on adding to the stdlib, -0 on putting on PyPI (I wouldn't bother looking on PyPI for something like this - I'd just write my own, specific to the need). ChrisA

@Steven D'Aprano<mailto:steve@pearwood.info> , I know you didn't ask for the implementation, but it does supports the idea and it contains comments describing how it's used. @Chris Angelico<mailto:rosuav@gmail.com>, I don't know if it needs to be further customizable (apart from a repeat keyword maybe) as it's meant to be similar to the timeit function, and it's not meant to be complicated + you can use this argument for basically any function in the standard library as it might not be customizable enough for someone's needs, I don't know if that's a big problem in the first place as there are functions in the standard library with lots of keywords (e.g the Popen constructor with 18 kw). I see that it deserves to be in the std library as the current way might not be suitable for many, i mean: `timeit.timeit("func_to_time(a, b)", setup="from __main__ import a, b") or `timeit.timeit("func_to_time(a, b)", globals=globals())` is not as: ``` timef func_to_time(a, b): ... ``` when you don't want it for now you simply comment the decorator. Also consider the case where you have a dozen of functions and methods to measure, it'll be tedious to go with timeit version. in addition other languages that compete with python have easy ways to measure times in their standard library that are more pythonic than python itself (e.g Julia with @timev, @timed, @elapsed macros, Kotlin with the measureTimeMillis and measureNanoTime functions ...) and the implementation is open for change, it can be made better. ________________________________ De : Python-ideas <python-ideas-bounces+amjadhedhili=outlook.com@python.org> de la part de Chris Angelico <rosuav@gmail.com> Envoyé : dimanche 7 octobre 2018 05:44 À : python-ideas Objet : Re: [Python-ideas] add a time decorator to timeit.py On Sun, Oct 7, 2018 at 11:34 PM Steven D'Aprano <steve@pearwood.info> wrote:
Or just too detaily. The given function has four keyword arguments that vary its behaviour slightly; if it gets into the stdlib, someone will ask for a way to control the output format, someone else will request that it be able to send to stderr instead of stdout, etc, etc, and suddenly the proposed function has two dozen kwargs. One of the powerful tools available to a programmer is having *your own* debug library, without having to worry about what other people would do that might be similar. -0.5 on adding to the stdlib, -0 on putting on PyPI (I wouldn't bother looking on PyPI for something like this - I'd just write my own, specific to the need). ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/

Summary: Python's timeit.timeit() has an undocumented feature / implementation detail that gives much of what the original poster has asked for. Perhaps revising the docs will solve the problem. This thread has prompted me to look at timeit again. Usually, I look at the command line help first.
This time, to my surprise, I found the following works:
Until today, as I recall, I didn't know this. Now for: https://docs.python.org/3/library/timeit.html I don't see any examples there, that show that timeit.timeit can take a callable as its first argument. So my ignorance can, I hope be forgiven. Now for: https://github.com/python/cpython/blob/3.7/Lib/timeit.py#L100 This contains, for both the stmt and setup parameters, explicit tests such as if isinstance(stmt, str): # string case elif callable(stmt): # callable case So I think it's an undocumented feature, rather than an implementation detail. And if you're a software historian, now perhaps look at https://github.com/python/cpython/commits/3.7/Lib/timeit.py And also, if you wish, for the tests for timeit.py. -- Jonathan

Summary: It's an undocumented feature, not an implementation detail. I wrote:
And if you're a software historian, now perhaps look at https://github.com/python/cpython/commits/3.7/Lib/timeit.py
Well, I've bitten my own bait, and have found it. See https://github.com/python/cpython/commit/d8faa3654 Merged revisions 53952-54987 via svnmerge from svn+ssh://pythondev@svn.python.org/python/trunk This is a compendium commit, which contains r54348 | georg.brandl | 2007-03-13 12:32:21 -0700 (Tue, 13 Mar 2007) | 4 lines Patch #1533909: the timeit module now accepts callables in addition to strings for the code to time and the setup code. Also added two convenience functions for instantiating a Timer and calling its methods. For the diff see https://github.com/python/cpython/commit/d8faa3654#diff-f21e8a2e7addb312903b... So to me that's conclusive. An undocumented feature. -- Jonathan

although timeit can be used with a callable, you need to create a lambda expression if the function has args: ``` def func_to_time(a, b): ... timeit.timeit(lambda: func_to_time(a, b), globals=globals()) ``` and you can't use it as a decorator. ________________________________ De : Python-ideas <python-ideas-bounces+amjadhedhili=outlook.com@python.org> de la part de Jonathan Fine <jfine2358@gmail.com> Envoyé : dimanche 7 octobre 2018 09:15 À : python-ideas Objet : Re: [Python-ideas] add a time decorator to timeit.py Summary: Python's timeit.timeit() has an undocumented feature / implementation detail that gives much of what the original poster has asked for. Perhaps revising the docs will solve the problem. This thread has prompted me to look at timeit again. Usually, I look at the command line help first.
This time, to my surprise, I found the following works:
Until today, as I recall, I didn't know this. Now for: https://docs.python.org/3/library/timeit.html I don't see any examples there, that show that timeit.timeit can take a callable as its first argument. So my ignorance can, I hope be forgiven. Now for: https://github.com/python/cpython/blob/3.7/Lib/timeit.py#L100 This contains, for both the stmt and setup parameters, explicit tests such as if isinstance(stmt, str): # string case elif callable(stmt): # callable case So I think it's an undocumented feature, rather than an implementation detail. And if you're a software historian, now perhaps look at https://github.com/python/cpython/commits/3.7/Lib/timeit.py And also, if you wish, for the tests for timeit.py. -- Jonathan _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/

On Sun, Oct 07, 2018 at 10:16:08AM +0000, Amjad Ben Hedhili wrote:
There are lots of ways to call timeit, but generally if you are calling it with number=1 then your results may not be trustworthy. (That depends on the function, of course.) There's a reason why timeit defaults to number=1000000 rather than 1.
The problem with that as an API is that once you have decorated the function to call timeit, how do you use it *without* calling timeit? If you use functools.wraps, there will be a func_to_time.__wrapped__ but I don't think that calling that directly is a good interface.
Can you explain what the timef decorator does? What arguments does it take, how do we use it? -- Steve

this is my implementation: ``` units = {"auto": -1, "s": 1, "ms": 1e3, "μs": 1e6, "ns": 1e9} def timef(unit="auto", number=1, garcol=True, get_time=False): """A decorator to measure the execution time of a function Returns the return of the tested function if get_time is False or the execution time in unit if not unit: one of "ns" (nanoseconds), "μs" (microseconds), "ms" (milliseconds), "s" (seconds) and "auto": (automatic) (default: "auto") garcol: if True enables garbage collection (default: True) get_time: if true return the execution time of the function (default: False) """ fun = False if callable(unit): fun = True func = unit unit = "auto" elif unit not in units: raise ValueError( "valid options: " "s(seconds), " "ms(milliseconds), " "μs(microseconds), " "ns(nanoseconds) " "and auto(automatic: default)") def decorating_function(func): return _wrapper(func, number, units, unit, units[unit], garcol, get_time) return decorating_function(func) if fun else decorating_function def _wrapper(func, number, units, unit, factor, garcol, get_time): def timed(*args, **kwargs): nonlocal unit, factor gcold = gc.isenabled() # disable garbage collection on demand if not garcol: gc.disable() try: if number > 0: start = default_timer() for _ in range(number): func(*args, **kwargs) time = default_timer() - start result = func(*args, **kwargs) finally: if gcold: gc.enable() if unit == "auto": for u, f in units.items(): if 1 <= int(time * f) < 1000: unit, factor = u, f break else: unit, factor = "s", 1 time *= factor print(f"{func.__qualname__}: {time:.4f} {unit}") return time if get_time else result return timed ``` it's not perfect but it works; you can control number of repetitions, garbage collection, unit ... and it produces a formatted output such as: `fun_to_time: 24.1056 ms`. ________________________________ De : Python-ideas <python-ideas-bounces+amjadhedhili=outlook.com@python.org> de la part de Steven D'Aprano <steve@pearwood.info> Envoyé : dimanche 7 octobre 2018 04:15 À : python-ideas@python.org Objet : Re: [Python-ideas] add a time decorator to timeit.py On Sun, Oct 07, 2018 at 10:16:08AM +0000, Amjad Ben Hedhili wrote:
There are lots of ways to call timeit, but generally if you are calling it with number=1 then your results may not be trustworthy. (That depends on the function, of course.) There's a reason why timeit defaults to number=1000000 rather than 1.
The problem with that as an API is that once you have decorated the function to call timeit, how do you use it *without* calling timeit? If you use functools.wraps, there will be a func_to_time.__wrapped__ but I don't think that calling that directly is a good interface.
Can you explain what the timef decorator does? What arguments does it take, how do we use it? -- Steve _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/

On Sun, Oct 07, 2018 at 11:43:40AM +0000, Amjad Ben Hedhili wrote:
this is my implementation:
I didn't ask about the current implementation, as that might change. I asked about the interface: what it does, not how it does it. For the purposes of Python-Ideas, we want to discuss the feature, not the details of how it works, why it is better than other existing solutions, and whether or not it belongs in the std lib. You're selling the *idea*, not the *implementation* (although of course you can use the implementation to demonstrate the idea). Unfortunately, not every good idea can or will make it into the std lib. Even if the idea is good, we might decide that it belongs as a third- party library, or that the benefit is not enough to make up for the cost in effort or complexity. Or the idea is simply too controversial. -- Steve

On Sun, Oct 7, 2018 at 11:34 PM Steven D'Aprano <steve@pearwood.info> wrote:
Or just too detaily. The given function has four keyword arguments that vary its behaviour slightly; if it gets into the stdlib, someone will ask for a way to control the output format, someone else will request that it be able to send to stderr instead of stdout, etc, etc, and suddenly the proposed function has two dozen kwargs. One of the powerful tools available to a programmer is having *your own* debug library, without having to worry about what other people would do that might be similar. -0.5 on adding to the stdlib, -0 on putting on PyPI (I wouldn't bother looking on PyPI for something like this - I'd just write my own, specific to the need). ChrisA

@Steven D'Aprano<mailto:steve@pearwood.info> , I know you didn't ask for the implementation, but it does supports the idea and it contains comments describing how it's used. @Chris Angelico<mailto:rosuav@gmail.com>, I don't know if it needs to be further customizable (apart from a repeat keyword maybe) as it's meant to be similar to the timeit function, and it's not meant to be complicated + you can use this argument for basically any function in the standard library as it might not be customizable enough for someone's needs, I don't know if that's a big problem in the first place as there are functions in the standard library with lots of keywords (e.g the Popen constructor with 18 kw). I see that it deserves to be in the std library as the current way might not be suitable for many, i mean: `timeit.timeit("func_to_time(a, b)", setup="from __main__ import a, b") or `timeit.timeit("func_to_time(a, b)", globals=globals())` is not as: ``` timef func_to_time(a, b): ... ``` when you don't want it for now you simply comment the decorator. Also consider the case where you have a dozen of functions and methods to measure, it'll be tedious to go with timeit version. in addition other languages that compete with python have easy ways to measure times in their standard library that are more pythonic than python itself (e.g Julia with @timev, @timed, @elapsed macros, Kotlin with the measureTimeMillis and measureNanoTime functions ...) and the implementation is open for change, it can be made better. ________________________________ De : Python-ideas <python-ideas-bounces+amjadhedhili=outlook.com@python.org> de la part de Chris Angelico <rosuav@gmail.com> Envoyé : dimanche 7 octobre 2018 05:44 À : python-ideas Objet : Re: [Python-ideas] add a time decorator to timeit.py On Sun, Oct 7, 2018 at 11:34 PM Steven D'Aprano <steve@pearwood.info> wrote:
Or just too detaily. The given function has four keyword arguments that vary its behaviour slightly; if it gets into the stdlib, someone will ask for a way to control the output format, someone else will request that it be able to send to stderr instead of stdout, etc, etc, and suddenly the proposed function has two dozen kwargs. One of the powerful tools available to a programmer is having *your own* debug library, without having to worry about what other people would do that might be similar. -0.5 on adding to the stdlib, -0 on putting on PyPI (I wouldn't bother looking on PyPI for something like this - I'd just write my own, specific to the need). ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/

Summary: Python's timeit.timeit() has an undocumented feature / implementation detail that gives much of what the original poster has asked for. Perhaps revising the docs will solve the problem. This thread has prompted me to look at timeit again. Usually, I look at the command line help first.
This time, to my surprise, I found the following works:
Until today, as I recall, I didn't know this. Now for: https://docs.python.org/3/library/timeit.html I don't see any examples there, that show that timeit.timeit can take a callable as its first argument. So my ignorance can, I hope be forgiven. Now for: https://github.com/python/cpython/blob/3.7/Lib/timeit.py#L100 This contains, for both the stmt and setup parameters, explicit tests such as if isinstance(stmt, str): # string case elif callable(stmt): # callable case So I think it's an undocumented feature, rather than an implementation detail. And if you're a software historian, now perhaps look at https://github.com/python/cpython/commits/3.7/Lib/timeit.py And also, if you wish, for the tests for timeit.py. -- Jonathan

Summary: It's an undocumented feature, not an implementation detail. I wrote:
And if you're a software historian, now perhaps look at https://github.com/python/cpython/commits/3.7/Lib/timeit.py
Well, I've bitten my own bait, and have found it. See https://github.com/python/cpython/commit/d8faa3654 Merged revisions 53952-54987 via svnmerge from svn+ssh://pythondev@svn.python.org/python/trunk This is a compendium commit, which contains r54348 | georg.brandl | 2007-03-13 12:32:21 -0700 (Tue, 13 Mar 2007) | 4 lines Patch #1533909: the timeit module now accepts callables in addition to strings for the code to time and the setup code. Also added two convenience functions for instantiating a Timer and calling its methods. For the diff see https://github.com/python/cpython/commit/d8faa3654#diff-f21e8a2e7addb312903b... So to me that's conclusive. An undocumented feature. -- Jonathan

although timeit can be used with a callable, you need to create a lambda expression if the function has args: ``` def func_to_time(a, b): ... timeit.timeit(lambda: func_to_time(a, b), globals=globals()) ``` and you can't use it as a decorator. ________________________________ De : Python-ideas <python-ideas-bounces+amjadhedhili=outlook.com@python.org> de la part de Jonathan Fine <jfine2358@gmail.com> Envoyé : dimanche 7 octobre 2018 09:15 À : python-ideas Objet : Re: [Python-ideas] add a time decorator to timeit.py Summary: Python's timeit.timeit() has an undocumented feature / implementation detail that gives much of what the original poster has asked for. Perhaps revising the docs will solve the problem. This thread has prompted me to look at timeit again. Usually, I look at the command line help first.
This time, to my surprise, I found the following works:
Until today, as I recall, I didn't know this. Now for: https://docs.python.org/3/library/timeit.html I don't see any examples there, that show that timeit.timeit can take a callable as its first argument. So my ignorance can, I hope be forgiven. Now for: https://github.com/python/cpython/blob/3.7/Lib/timeit.py#L100 This contains, for both the stmt and setup parameters, explicit tests such as if isinstance(stmt, str): # string case elif callable(stmt): # callable case So I think it's an undocumented feature, rather than an implementation detail. And if you're a software historian, now perhaps look at https://github.com/python/cpython/commits/3.7/Lib/timeit.py And also, if you wish, for the tests for timeit.py. -- Jonathan _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
participants (4)
-
Amjad Ben Hedhili
-
Chris Angelico
-
Jonathan Fine
-
Steven D'Aprano