Skip to content

Commit c59b4df

Browse files
committed
use argparse instead of getopt in timeit
1 parent b07a267 commit c59b4df

File tree

2 files changed

+89
-95
lines changed

2 files changed

+89
-95
lines changed

Lib/test/test_timeit.py

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -252,11 +252,9 @@ def run_main(self, seconds_per_increment=1.0, switches=None, timer=None):
252252
return s.getvalue()
253253

254254
def test_main_bad_switch(self):
255-
s = self.run_main(switches=['--bad-switch'])
256-
self.assertEqual(s, dedent("""\
257-
option --bad-switch not recognized
258-
use -h/--help for command line help
259-
"""))
255+
with self.assertRaises(SystemExit), captured_stderr() as s:
256+
self.run_main(switches=['--bad-switch'])
257+
self.assertIn("unrecognized arguments: --bad-switch", s.getvalue())
260258

261259
def test_main_seconds(self):
262260
s = self.run_main(seconds_per_increment=5.5)
@@ -294,11 +292,6 @@ def test_main_negative_reps(self):
294292
s = self.run_main(seconds_per_increment=60.0, switches=['-r-5'])
295293
self.assertEqual(s, "1 loop, best of 1: 60 sec per loop\n")
296294

297-
@unittest.skipIf(sys.flags.optimize >= 2, "need __doc__")
298-
def test_main_help(self):
299-
s = self.run_main(switches=['-h'])
300-
self.assertEqual(s, timeit.__doc__)
301-
302295
def test_main_verbose(self):
303296
s = self.run_main(switches=['-v'])
304297
self.assertEqual(s, dedent("""\
@@ -345,11 +338,10 @@ def test_main_with_time_unit(self):
345338
self.assertEqual(unit_usec,
346339
"100 loops, best of 5: 3e+03 usec per loop\n")
347340
# Test invalid unit input
348-
with captured_stderr() as error_stringio:
349-
invalid = self.run_main(seconds_per_increment=0.003,
350-
switches=['-u', 'parsec'])
351-
self.assertEqual(error_stringio.getvalue(),
352-
"Unrecognized unit. Please select nsec, usec, msec, or sec.\n")
341+
with self.assertRaises(SystemExit), captured_stderr() as s:
342+
self.run_main(seconds_per_increment=0.003,
343+
switches=['-u', 'parsec'])
344+
self.assertIn("invalid choice: 'parsec'", s.getvalue())
353345

354346
def test_main_exception(self):
355347
with captured_stderr() as error_stringio:

Lib/timeit.py

Lines changed: 82 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,7 @@
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] [--] [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-
-h/--help: print this usage message and exit
21-
--: separate options from statement, use when statement starts with -
22-
statement: statement to be timed (default 'pass')
23-
24-
A multi-line statement may be given by specifying each line as a
25-
separate argument; indented lines are possible by enclosing an
26-
argument in quotes and using leading spaces. Multiple -s options are
27-
treated similarly.
28-
29-
If -n is not given, a suitable number of loops is calculated by trying
30-
increasing numbers from the sequence 1, 2, 5, 10, 20, 50, ... until the
31-
total time is at least 0.2 seconds.
32-
33-
Note: there is a certain baseline overhead associated with executing a
34-
pass statement. It differs between versions. The code here doesn't try
35-
to hide it, but you should be aware of it. The baseline overhead can be
36-
measured by invoking the program without arguments.
9+
Command line usage: python -m timeit --help
3710
3811
Classes:
3912
@@ -240,6 +213,71 @@ def repeat(stmt="pass", setup="pass", timer=default_timer,
240213
return Timer(stmt, setup, timer, globals).repeat(repeat, number)
241214

242215

216+
def _make_parser():
217+
import argparse
218+
219+
parser = argparse.ArgumentParser(
220+
description="""\
221+
Tool avoiding a number of common traps for measuring execution times.
222+
See also Tim Peters' introduction to the Algorithms chapter in the
223+
Python Cookbook, published by O'Reilly.""",
224+
epilog="""\
225+
A multi-line statement may be given by specifying each line as a
226+
separate argument; indented lines are possible by enclosing an
227+
argument in quotes and using leading spaces. Multiple -s options are
228+
treated similarly.
229+
230+
If -n is not given, a suitable number of loops is calculated by trying
231+
increasing numbers from the sequence 1, 2, 5, 10, 20, 50, ... until the
232+
total time is at least 0.2 seconds.
233+
234+
Note: there is a certain baseline overhead associated with executing a
235+
pass statement. It differs between versions. The code here doesn't try
236+
to hide it, but you should be aware of it. The baseline overhead can be
237+
measured by invoking the program without arguments.""",
238+
formatter_class=argparse.RawTextHelpFormatter,
239+
allow_abbrev=False,
240+
)
241+
# use a group to avoid rendering a 'positional arguments' section
242+
group = parser.add_argument_group()
243+
group.add_argument(
244+
"-n", "--number", metavar="N", default=0, type=int,
245+
help="how many times to execute 'statement' (default: see below)",
246+
)
247+
group.add_argument(
248+
"-r", "--repeat", type=int, metavar="N", default=default_repeat,
249+
help="how many times to repeat the timer (default: 5)",
250+
)
251+
group.add_argument(
252+
"-s", "--setup", action="append", metavar="S", default=[],
253+
help="statement to be executed once initially (default: 'pass').\n"
254+
"Execution time of this setup statement is NOT timed.",
255+
)
256+
group.add_argument(
257+
"-u", "--unit", choices=["nsec", "usec", "msec", "sec"], metavar="U",
258+
help="set the output time unit (nsec, usec, msec, or sec)",
259+
)
260+
group.add_argument(
261+
"-p", "--process", action="store_true",
262+
help="use time.process_time() (default: time.perf_counter())",
263+
)
264+
group.add_argument(
265+
"-v", "--verbose", action="count", default=0,
266+
help="print raw timing results; repeat for more digits precision",
267+
)
268+
# add a dummy option to document '--'
269+
group.add_argument(
270+
"--", dest="_", action="store_const", const=None,
271+
help="separate options from statement; "
272+
"use when statement starts with -",
273+
)
274+
group.add_argument(
275+
"statement", nargs="*",
276+
help="statement to be timed (default: 'pass')",
277+
)
278+
return parser
279+
280+
243281
def main(args=None, *, _wrap_timer=None):
244282
"""Main program, used when run as a script.
245283
@@ -257,53 +295,16 @@ def main(args=None, *, _wrap_timer=None):
257295
is not None, it must be a callable that accepts a timer function
258296
and returns another timer function (used for unit testing).
259297
"""
260-
if args is None:
261-
args = sys.argv[1:]
262-
import getopt
263-
try:
264-
opts, args = getopt.getopt(args, "n:u:s:r:pvh",
265-
["number=", "setup=", "repeat=",
266-
"process", "verbose", "unit=", "help"])
267-
except getopt.error as err:
268-
print(err)
269-
print("use -h/--help for command line help")
270-
return 2
271-
272-
timer = default_timer
273-
stmt = "\n".join(args) or "pass"
274-
number = 0 # auto-determine
275-
setup = []
276-
repeat = default_repeat
277-
verbose = 0
278-
time_unit = None
279-
units = {"nsec": 1e-9, "usec": 1e-6, "msec": 1e-3, "sec": 1.0}
280-
precision = 3
281-
for o, a in opts:
282-
if o in ("-n", "--number"):
283-
number = int(a)
284-
if o in ("-s", "--setup"):
285-
setup.append(a)
286-
if o in ("-u", "--unit"):
287-
if a in units:
288-
time_unit = a
289-
else:
290-
print("Unrecognized unit. Please select nsec, usec, msec, or sec.",
291-
file=sys.stderr)
292-
return 2
293-
if o in ("-r", "--repeat"):
294-
repeat = int(a)
295-
if repeat <= 0:
296-
repeat = 1
297-
if o in ("-p", "--process"):
298-
timer = time.process_time
299-
if o in ("-v", "--verbose"):
300-
if verbose:
301-
precision += 1
302-
verbose += 1
303-
if o in ("-h", "--help"):
304-
print(__doc__, end="")
305-
return 0
306-
setup = "\n".join(setup) or "pass"
298+
parser = _make_parser()
299+
args = parser.parse_args(args)
300+
301+
setup = "\n".join(args.setup) or "pass"
302+
stmt = "\n".join(args.statement) or "pass"
303+
timer = time.process_time if args.process else default_timer
304+
number = args.number # will be deduced if 0
305+
repeat = max(1, args.repeat)
306+
verbose = bool(args.verbose)
307+
precision = 3 + max(0, args.verbose - 1)
307308

308309
# Include the current directory, so that local imports work (sys.path
309310
# contains the directory of this script, rather than the current
@@ -338,14 +339,16 @@ def callback(number, time_taken):
338339
t.print_exc()
339340
return 1
340341

342+
units = {"nsec": 1e-9, "usec": 1e-6, "msec": 1e-3, "sec": 1.0}
343+
scales = [(scale, unit_name) for unit_name, scale in units.items()]
344+
scales.sort(reverse=True)
345+
341346
def format_time(dt):
342-
unit = time_unit
347+
unit = args.unit
343348

344349
if unit is not None:
345350
scale = units[unit]
346351
else:
347-
scales = [(scale, unit) for unit, scale in units.items()]
348-
scales.sort(reverse=True)
349352
for scale, unit in scales:
350353
if dt >= scale:
351354
break
@@ -362,7 +365,6 @@ def format_time(dt):
362365
% (number, 's' if number != 1 else '',
363366
repeat, format_time(best)))
364367

365-
best = min(timings)
366368
worst = max(timings)
367369
if worst >= best * 4:
368370
import warnings
@@ -371,7 +373,7 @@ def format_time(dt):
371373
"slower than the best time (%s)."
372374
% (format_time(worst), format_time(best)),
373375
UserWarning, '', 0)
374-
return None
376+
return 0
375377

376378

377379
if __name__ == "__main__":

0 commit comments

Comments
 (0)