Skip to content

Commit 99835c7

Browse files
committed
Use argparse for colour help timeit CLI
1 parent 68fe899 commit 99835c7

3 files changed

Lines changed: 115 additions & 90 deletions

File tree

Lib/test/test_timeit.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -259,11 +259,12 @@ def run_main(self, seconds_per_increment=1.0, switches=None, timer=None):
259259
return s.getvalue()
260260

261261
def test_main_bad_switch(self):
262-
s = self.run_main(switches=['--bad-switch'])
263-
self.assertEqual(s, dedent("""\
264-
option --bad-switch not recognized
265-
use -h/--help for command line help
266-
"""))
262+
with captured_stderr() as error_stringio:
263+
s = self.run_main(switches=["--bad-switch"])
264+
self.assertEqual(s, "")
265+
self.assertIn(
266+
"unrecognized arguments: --bad-switch", error_stringio.getvalue()
267+
)
267268

268269
def test_main_seconds(self):
269270
s = self.run_main(seconds_per_increment=5.5)
@@ -301,10 +302,11 @@ def test_main_negative_reps(self):
301302
s = self.run_main(seconds_per_increment=60.0, switches=['-r-5'])
302303
self.assertEqual(s, "1 loop, best of 1: 60 sec per loop\n")
303304

304-
@unittest.skipIf(sys.flags.optimize >= 2, "need __doc__")
305305
def test_main_help(self):
306306
s = self.run_main(switches=['-h'])
307-
self.assertEqual(s, timeit.__doc__)
307+
self.assertIn("Tool for measuring execution time", s)
308+
self.assertIn("-n", s)
309+
self.assertIn("--number", s)
308310

309311
def test_main_verbose(self):
310312
s = self.run_main(switches=['-v'])
@@ -353,10 +355,12 @@ def test_main_with_time_unit(self):
353355
"100 loops, best of 5: 3e+03 usec per loop\n")
354356
# Test invalid unit input
355357
with captured_stderr() as error_stringio:
356-
invalid = self.run_main(seconds_per_increment=0.003,
357-
switches=['-u', 'parsec'])
358-
self.assertEqual(error_stringio.getvalue(),
359-
"Unrecognized unit. Please select nsec, usec, msec, or sec.\n")
358+
invalid = self.run_main(
359+
seconds_per_increment=0.003, switches=["-u", "parsec"]
360+
)
361+
self.assertIn(
362+
"choose from nsec, usec, msec, sec", error_stringio.getvalue()
363+
)
360364

361365
def test_main_exception(self):
362366
with captured_stderr() as error_stringio:

Lib/timeit.py

Lines changed: 99 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -6,38 +6,6 @@
66
77
Library usage: see the Timer class.
88
9-
Command line usage:
10-
python timeit.py [-n N] [-r N] [-s S] [-p] [-h] [-t T] [--] [statement]
11-
12-
Options:
13-
-n/--number N: how many times to execute 'statement' (default: see below)
14-
-r/--repeat N: how many times to repeat the timer (default 5)
15-
-s/--setup S: statement to be executed once initially (default 'pass').
16-
Execution time of this setup statement is NOT timed.
17-
-p/--process: use time.process_time() (default is time.perf_counter())
18-
-v/--verbose: print raw timing results; repeat for more digits precision
19-
-u/--unit: set the output time unit (nsec, usec, msec, or sec)
20-
-t/--target-time T: if --number is 0 the code will run until it
21-
takes *at least* this many seconds
22-
(default: 0.2)
23-
-h/--help: print this usage message and exit
24-
--: separate options from statement, use when statement starts with -
25-
statement: statement to be timed (default 'pass')
26-
27-
A multi-line statement may be given by specifying each line as a
28-
separate argument; indented lines are possible by enclosing an
29-
argument in quotes and using leading spaces. Multiple -s options are
30-
treated similarly.
31-
32-
If -n is not given, a suitable number of loops is calculated by trying
33-
increasing numbers from the sequence 1, 2, 5, 10, 20, 50, ... until the
34-
total time is at least --target-time seconds.
35-
36-
Note: there is a certain baseline overhead associated with executing a
37-
pass statement. It differs between versions. The code here doesn't try
38-
to hide it, but you should be aware of it. The baseline overhead can be
39-
measured by invoking the program without arguments.
40-
419
Classes:
4210
4311
Timer
@@ -268,62 +236,114 @@ def main(args=None, *, _wrap_timer=None):
268236
is not None, it must be a callable that accepts a timer function
269237
and returns another timer function (used for unit testing).
270238
"""
271-
import getopt
239+
import argparse
272240
if args is None:
273241
args = sys.argv[1:]
274242
import _colorize
275243
colorize = _colorize.can_colorize()
276244
theme = _colorize.get_theme(force_color=colorize).timeit
277245
reset = theme.reset
278246

247+
epilog = """\
248+
A multi-line statement may be given by specifying each line as a
249+
separate argument; indented lines are possible by enclosing an
250+
argument in quotes and using leading spaces. Multiple `-s` options are
251+
treated similarly.
252+
253+
If `-n` is not given, a suitable number of loops is calculated by trying
254+
increasing numbers from the sequence 1, 2, 5, 10, 20, 50, ... until the
255+
total time is at least `--target-time` seconds.
256+
257+
Note: there is a certain baseline overhead associated with executing a
258+
pass statement. It differs between versions. The code here doesn't try
259+
to hide it, but you should be aware of it. The baseline overhead can be
260+
measured by invoking the program without arguments."""
261+
262+
parser = argparse.ArgumentParser(
263+
prog="python -m timeit",
264+
description="""\
265+
Tool for measuring execution time of small code snippets.
266+
267+
This module avoids a number of common traps for measuring execution
268+
times. See also Tim Peters' introduction to the Algorithms chapter in
269+
the Python Cookbook, published by O'Reilly.
270+
271+
Library usage: see the Timer class.""",
272+
epilog=epilog,
273+
formatter_class=argparse.RawDescriptionHelpFormatter,
274+
)
275+
parser.add_argument(
276+
"-n",
277+
"--number",
278+
type=int,
279+
default=0,
280+
help="how many times to execute 'statement' (default: see below)",
281+
)
282+
parser.add_argument(
283+
"-r",
284+
"--repeat",
285+
type=int,
286+
default=default_repeat,
287+
help="how many times to repeat the timer (default %(default)s)",
288+
)
289+
parser.add_argument(
290+
"-s",
291+
"--setup",
292+
action="append",
293+
default=[],
294+
help="statement to be executed once initially "
295+
"(default 'pass'). Execution time of this "
296+
"setup statement is NOT timed.",
297+
)
298+
parser.add_argument(
299+
"-p",
300+
"--process",
301+
action="store_true",
302+
help="use time.process_time() (default is time.perf_counter())",
303+
)
304+
parser.add_argument(
305+
"-t",
306+
"--target-time",
307+
type=float,
308+
default=default_target_time,
309+
help="if --number is 0 the code will run until it takes "
310+
"at least this many seconds (default %(default)s)",
311+
)
312+
parser.add_argument(
313+
"-v",
314+
"--verbose",
315+
action="count",
316+
default=0,
317+
help="print raw timing results; repeat for more digits precision",
318+
)
319+
parser.add_argument(
320+
"-u",
321+
"--unit",
322+
default=None,
323+
choices=["nsec", "usec", "msec", "sec"],
324+
help="set the output time unit",
325+
)
326+
parser.add_argument(
327+
"statement",
328+
nargs="*",
329+
default=["pass"],
330+
help="statement to be timed (default 'pass')",
331+
)
279332
try:
280-
opts, args = getopt.getopt(args, "n:u:s:r:pt:vh",
281-
["number=", "setup=", "repeat=",
282-
"process", "target-time=",
283-
"verbose", "unit=", "help"])
284-
except getopt.error as err:
285-
print(err)
286-
print("use -h/--help for command line help")
287-
return 2
288-
289-
timer = default_timer
290-
stmt = "\n".join(args) or "pass"
291-
number = 0 # auto-determine
292-
target_time = default_target_time
293-
setup = []
294-
repeat = default_repeat
295-
verbose = 0
296-
time_unit = None
333+
ns = parser.parse_args(args)
334+
except SystemExit as e:
335+
return e.code
336+
337+
timer = time.process_time if ns.process else default_timer
338+
stmt = "\n".join(ns.statement) or "pass"
339+
number = ns.number
340+
target_time = ns.target_time
341+
setup = "\n".join(ns.setup) or "pass"
342+
repeat = max(ns.repeat, 1)
343+
verbose = ns.verbose
344+
time_unit = ns.unit
297345
units = {"nsec": 1e-9, "usec": 1e-6, "msec": 1e-3, "sec": 1.0}
298-
precision = 3
299-
for o, a in opts:
300-
if o in ("-n", "--number"):
301-
number = int(a)
302-
if o in ("-s", "--setup"):
303-
setup.append(a)
304-
if o in ("-u", "--unit"):
305-
if a in units:
306-
time_unit = a
307-
else:
308-
print("Unrecognized unit. Please select nsec, usec, msec, or sec.",
309-
file=sys.stderr)
310-
return 2
311-
if o in ("-r", "--repeat"):
312-
repeat = int(a)
313-
if repeat <= 0:
314-
repeat = 1
315-
if o in ("-p", "--process"):
316-
timer = time.process_time
317-
if o in ("-t", "--target-time"):
318-
target_time = float(a)
319-
if o in ("-v", "--verbose"):
320-
if verbose:
321-
precision += 1
322-
verbose += 1
323-
if o in ("-h", "--help"):
324-
print(__doc__, end="")
325-
return 0
326-
setup = "\n".join(setup) or "pass"
346+
precision = 3 + max(verbose - 1, 0)
327347

328348
# Include the current directory, so that local imports work (sys.path
329349
# contains the directory of this script, rather than the current
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Use argparse for colour help timeit CLI. Patch by Hugo van Kemenade.

0 commit comments

Comments
 (0)