Skip to content

Commit 94dfd6b

Browse files
alessandrocucciacuccie3
authored andcommitted
- make the 0.2s time configurable;
- have `timeit` and `repeat` methods (and functions) fall back on `autorange` if the number is set to 0 or None.
1 parent 830b43d commit 94dfd6b

File tree

3 files changed

+65
-27
lines changed

3 files changed

+65
-27
lines changed

Doc/library/timeit.rst

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,29 +58,36 @@ Python Interface
5858
The module defines three convenience functions and a public class:
5959

6060

61-
.. function:: timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None)
61+
.. function:: timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None, max_time_taken=0.2)
6262

6363
Create a :class:`Timer` instance with the given statement, *setup* code and
6464
*timer* function and run its :meth:`.timeit` method with *number* executions.
6565
The optional *globals* argument specifies a namespace in which to execute the
66-
code.
66+
code. If *number* is 0, :meth:`.autorange` method is executed, a convenience
67+
function that calls :meth:`.timeit` repeatedly so that the total time >= *max_time_taken* second.
6768

6869
.. versionchanged:: 3.5
6970
The optional *globals* parameter was added.
7071

72+
.. versionchanged:: 3.7
73+
The optional *max_time_taken* parameter was added.
74+
7175

72-
.. function:: repeat(stmt='pass', setup='pass', timer=<default timer>, repeat=5, number=1000000, globals=None)
76+
.. function:: repeat(stmt='pass', setup='pass', timer=<default timer>, repeat=5, number=1000000, globals=None, max_time_taken=0.2)
7377

7478
Create a :class:`Timer` instance with the given statement, *setup* code and
7579
*timer* function and run its :meth:`.repeat` method with the given *repeat*
7680
count and *number* executions. The optional *globals* argument specifies a
77-
namespace in which to execute the code.
81+
namespace in which to execute the code. If *number* is 0, :meth:`.autorange`
82+
method is executed, a convenience function that calls :meth:`.timeit` repeatedly
83+
so that the total time >= *max_time_taken* second.
7884

7985
.. versionchanged:: 3.5
8086
The optional *globals* parameter was added.
8187

8288
.. versionchanged:: 3.7
8389
Default value of *repeat* changed from 3 to 5.
90+
The optional *max_time_taken* parameter was added.
8491

8592
.. function:: default_timer()
8693

@@ -90,7 +97,7 @@ The module defines three convenience functions and a public class:
9097
:func:`time.perf_counter` is now the default timer.
9198

9299

93-
.. class:: Timer(stmt='pass', setup='pass', timer=<timer function>, globals=None)
100+
.. class:: Timer(stmt='pass', setup='pass', timer=<timer function>, globals=None, max_time_taken=0.2)
94101

95102
Class for timing execution speed of small code snippets.
96103

@@ -116,6 +123,9 @@ The module defines three convenience functions and a public class:
116123
.. versionchanged:: 3.5
117124
The optional *globals* parameter was added.
118125

126+
.. versionchanged:: 3.7
127+
The optional *max_time_taken* parameter was added.
128+
119129
.. method:: Timer.timeit(number=1000000)
120130

121131
Time *number* executions of the main statement. This executes the setup
@@ -142,10 +152,10 @@ The module defines three convenience functions and a public class:
142152
Automatically determine how many times to call :meth:`.timeit`.
143153

144154
This is a convenience function that calls :meth:`.timeit` repeatedly
145-
so that the total time >= 0.2 second, returning the eventual
155+
so that the total time >= *Timer.max_time_taken* second, returning the eventual
146156
(number of loops, time taken for that number of loops). It calls
147157
:meth:`.timeit` with increasing numbers from the sequence 1, 2, 5,
148-
10, 20, 50, ... until the time taken is at least 0.2 second.
158+
10, 20, 50, ... until the time taken is at least *max_time_taken* second.
149159

150160
If *callback* is given and is not ``None``, it will be called after
151161
each trial with two arguments: ``callback(number, time_taken)``.
@@ -202,7 +212,7 @@ Command-Line Interface
202212

203213
When called as a program from the command line, the following form is used::
204214

205-
python -m timeit [-n N] [-r N] [-u U] [-s S] [-h] [statement ...]
215+
python -m timeit [-n N] [-r N] [-u U] [-s S] [-m M] [-h] [statement ...]
206216

207217
Where the following options are understood:
208218

@@ -233,6 +243,12 @@ Where the following options are understood:
233243

234244
.. versionadded:: 3.5
235245

246+
.. cmdoption:: -m, --max_time_taken=M
247+
248+
calls :meth:`.timeit` repeatedly so that the total time >= *max_time_taken* second
249+
250+
.. versionadded:: 3.7
251+
236252
.. cmdoption:: -v, --verbose
237253

238254
print raw timing results; repeat for more digits precision

Lib/test/test_timeit.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,8 @@ def timeit(self, stmt, setup, number=None, globals=None):
107107
kwargs['number'] = number
108108
delta_time = t.timeit(**kwargs)
109109
self.assertEqual(self.fake_timer.setup_calls, 1)
110-
self.assertEqual(self.fake_timer.count, number)
111-
self.assertEqual(delta_time, number)
110+
self.assertEqual(self.fake_timer.count, number or 1)
111+
self.assertEqual(delta_time, number or (1, 1.0))
112112

113113
# Takes too long to run in debug build.
114114
#def test_timeit_default_iters(self):
@@ -139,7 +139,11 @@ def test_timeit_callable_stmt_and_setup(self):
139139
def test_timeit_function_zero_iters(self):
140140
delta_time = timeit.timeit(self.fake_stmt, self.fake_setup, number=0,
141141
timer=FakeTimer())
142-
self.assertEqual(delta_time, 0)
142+
self.assertEqual(delta_time, (1, 1.0))
143+
144+
def test_timeit_function_max_time_taken(self):
145+
delta_time = timeit.timeit(self.fake_stmt, self.fake_setup, number=0,
146+
timer=FakeTimer(), max_time_taken=1)
143147

144148
def test_timeit_globals_args(self):
145149
global _global_timer
@@ -152,9 +156,9 @@ def test_timeit_globals_args(self):
152156
timeit.timeit(stmt='local_timer.inc()', timer=local_timer,
153157
globals=locals(), number=3)
154158

155-
def repeat(self, stmt, setup, repeat=None, number=None):
159+
def repeat(self, stmt, setup, repeat=None, number=None, max_time_taken=0.5):
156160
self.fake_timer = FakeTimer()
157-
t = timeit.Timer(stmt=stmt, setup=setup, timer=self.fake_timer)
161+
t = timeit.Timer(stmt=stmt, setup=setup, timer=self.fake_timer, max_time_taken=max_time_taken)
158162
kwargs = {}
159163
if repeat is None:
160164
repeat = DEFAULT_REPEAT
@@ -166,8 +170,8 @@ def repeat(self, stmt, setup, repeat=None, number=None):
166170
kwargs['number'] = number
167171
delta_times = t.repeat(**kwargs)
168172
self.assertEqual(self.fake_timer.setup_calls, repeat)
169-
self.assertEqual(self.fake_timer.count, repeat * number)
170-
self.assertEqual(delta_times, repeat * [float(number)])
173+
self.assertEqual(self.fake_timer.count, (repeat * number) if number else repeat)
174+
self.assertEqual(delta_times, repeat * [float(number) or (1, 1.0)])
171175

172176
# Takes too long to run in debug build.
173177
#def test_repeat_default(self):
@@ -186,6 +190,10 @@ def test_repeat_callable_stmt(self):
186190
self.repeat(self.fake_callable_stmt, self.fake_setup,
187191
repeat=3, number=5)
188192

193+
def test_repeat_callable_max_time_taken(self):
194+
self.repeat(self.fake_callable_stmt, self.fake_setup,
195+
repeat=3, number=5, max_time_taken=1)
196+
189197
def test_repeat_callable_setup(self):
190198
self.repeat(self.fake_stmt, self.fake_callable_setup,
191199
repeat=3, number=5)
@@ -208,7 +216,7 @@ def test_repeat_function_zero_reps(self):
208216
def test_repeat_function_zero_iters(self):
209217
delta_times = timeit.repeat(self.fake_stmt, self.fake_setup, number=0,
210218
timer=FakeTimer())
211-
self.assertEqual(delta_times, DEFAULT_REPEAT * [0.0])
219+
self.assertEqual(delta_times, DEFAULT_REPEAT * [(1, 1.0)])
212220

213221
def assert_exc_string(self, exc_string, expected_exc_name):
214222
exc_lines = exc_string.splitlines()

Lib/timeit.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
default_number = 1000000
6060
default_repeat = 5
6161
default_timer = time.perf_counter
62+
default_time_taken = 0.2
6263

6364
_globals = globals
6465

@@ -75,10 +76,12 @@ def inner(_it, _timer{init}):
7576
return _t1 - _t0
7677
"""
7778

79+
7880
def reindent(src, indent):
7981
"""Helper to reindent a multi-line statement."""
8082
return src.replace("\n", "\n" + " "*indent)
8183

84+
8285
class Timer:
8386
"""Class for timing execution speed of small code snippets.
8487
@@ -98,9 +101,10 @@ class Timer:
98101
"""
99102

100103
def __init__(self, stmt="pass", setup="pass", timer=default_timer,
101-
globals=None):
104+
globals=None, max_time_taken=default_time_taken):
102105
"""Constructor. See class doc string."""
103106
self.timer = timer
107+
self.max_time_taken = max_time_taken
104108
local_ns = {}
105109
global_ns = _globals() if globals is None else globals
106110
init = ''
@@ -169,6 +173,8 @@ def timeit(self, number=default_number):
169173
to one million. The main statement, the setup statement and
170174
the timer function to be used are passed to the constructor.
171175
"""
176+
if not number:
177+
return self.autorange()
172178
it = itertools.repeat(None, number)
173179
gcold = gc.isenabled()
174180
gc.disable()
@@ -206,11 +212,12 @@ def repeat(self, repeat=default_repeat, number=default_number):
206212
return r
207213

208214
def autorange(self, callback=None):
209-
"""Return the number of loops and time taken so that total time >= 0.2.
215+
"""Return the number of loops and time taken so that total time >= max_time_taken
216+
(default is 0.2 seconds).
210217
211218
Calls the timeit method with increasing numbers from the sequence
212-
1, 2, 5, 10, 20, 50, ... until the time taken is at least 0.2
213-
second. Returns (number, time_taken).
219+
1, 2, 5, 10, 20, 50, ... until the max_time_taken is reached.
220+
Returns (number, time_taken).
214221
215222
If *callback* is given and is not None, it will be called after
216223
each trial with two arguments: ``callback(number, time_taken)``.
@@ -222,19 +229,22 @@ def autorange(self, callback=None):
222229
time_taken = self.timeit(number)
223230
if callback:
224231
callback(number, time_taken)
225-
if time_taken >= 0.2:
232+
if time_taken >= self.max_time_taken:
226233
return (number, time_taken)
227234
i *= 10
228235

236+
229237
def timeit(stmt="pass", setup="pass", timer=default_timer,
230-
number=default_number, globals=None):
238+
number=default_number, globals=None, max_time_taken=default_time_taken):
231239
"""Convenience function to create Timer object and call timeit method."""
232-
return Timer(stmt, setup, timer, globals).timeit(number)
240+
return Timer(stmt, setup, timer, globals, max_time_taken).timeit(number)
241+
233242

234243
def repeat(stmt="pass", setup="pass", timer=default_timer,
235-
repeat=default_repeat, number=default_number, globals=None):
244+
repeat=default_repeat, number=default_number, globals=None, max_time_taken=default_time_taken):
236245
"""Convenience function to create Timer object and call repeat method."""
237-
return Timer(stmt, setup, timer, globals).repeat(repeat, number)
246+
return Timer(stmt, setup, timer, globals, max_time_taken).repeat(repeat, number)
247+
238248

239249
def main(args=None, *, _wrap_timer=None):
240250
"""Main program, used when run as a script.
@@ -259,7 +269,7 @@ def main(args=None, *, _wrap_timer=None):
259269
try:
260270
opts, args = getopt.getopt(args, "n:u:s:r:tcpvh",
261271
["number=", "setup=", "repeat=",
262-
"time", "clock", "process",
272+
"time", "clock", "process", "max_time_taken="
263273
"verbose", "unit=", "help"])
264274
except getopt.error as err:
265275
print(err)
@@ -269,6 +279,7 @@ def main(args=None, *, _wrap_timer=None):
269279
timer = default_timer
270280
stmt = "\n".join(args) or "pass"
271281
number = 0 # auto-determine
282+
max_time_taken = default_time_taken
272283
setup = []
273284
repeat = default_repeat
274285
verbose = 0
@@ -293,6 +304,8 @@ def main(args=None, *, _wrap_timer=None):
293304
repeat = 1
294305
if o in ("-p", "--process"):
295306
timer = time.process_time
307+
if o in ("-m", "--max_time_taken"):
308+
max_time_taken = a
296309
if o in ("-v", "--verbose"):
297310
if verbose:
298311
precision += 1
@@ -310,7 +323,7 @@ def main(args=None, *, _wrap_timer=None):
310323
if _wrap_timer is not None:
311324
timer = _wrap_timer(timer)
312325

313-
t = Timer(stmt, setup, timer)
326+
t = Timer(stmt, setup, timer, max_time_taken=max_time_taken)
314327
if number == 0:
315328
# determine number so that 0.2 <= total time < 2.0
316329
callback = None
@@ -370,5 +383,6 @@ def format_time(dt):
370383
UserWarning, '', 0)
371384
return None
372385

386+
373387
if __name__ == "__main__":
374388
sys.exit(main())

0 commit comments

Comments
 (0)