This commit is contained in:
Hugo van Kemenade 2026-05-03 23:53:49 +03:00 committed by GitHub
commit fbce46477a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 115 additions and 90 deletions

View file

@ -259,11 +259,12 @@ def run_main(self, seconds_per_increment=1.0, switches=None, timer=None):
return s.getvalue()
def test_main_bad_switch(self):
s = self.run_main(switches=['--bad-switch'])
self.assertEqual(s, dedent("""\
option --bad-switch not recognized
use -h/--help for command line help
"""))
with captured_stderr() as error_stringio:
s = self.run_main(switches=["--bad-switch"])
self.assertEqual(s, "")
self.assertIn(
"unrecognized arguments: --bad-switch", error_stringio.getvalue()
)
def test_main_seconds(self):
s = self.run_main(seconds_per_increment=5.5)
@ -301,10 +302,11 @@ def test_main_negative_reps(self):
s = self.run_main(seconds_per_increment=60.0, switches=['-r-5'])
self.assertEqual(s, "1 loop, best of 1: 60 sec per loop\n")
@unittest.skipIf(sys.flags.optimize >= 2, "need __doc__")
def test_main_help(self):
s = self.run_main(switches=['-h'])
self.assertEqual(s, timeit.__doc__)
self.assertIn("Tool for measuring execution time", s)
self.assertIn("-n", s)
self.assertIn("--number", s)
def test_main_verbose(self):
s = self.run_main(switches=['-v'])
@ -353,10 +355,12 @@ def test_main_with_time_unit(self):
"100 loops, best of 5: 3e+03 usec per loop\n")
# Test invalid unit input
with captured_stderr() as error_stringio:
invalid = self.run_main(seconds_per_increment=0.003,
switches=['-u', 'parsec'])
self.assertEqual(error_stringio.getvalue(),
"Unrecognized unit. Please select nsec, usec, msec, or sec.\n")
invalid = self.run_main(
seconds_per_increment=0.003, switches=["-u", "parsec"]
)
self.assertIn(
"choose from nsec, usec, msec, sec", error_stringio.getvalue()
)
def test_main_exception(self):
with captured_stderr() as error_stringio:

View file

@ -6,38 +6,6 @@
Library usage: see the Timer class.
Command line usage:
python timeit.py [-n N] [-r N] [-s S] [-p] [-h] [-t T] [--] [statement]
Options:
-n/--number N: how many times to execute 'statement' (default: see below)
-r/--repeat N: how many times to repeat the timer (default 5)
-s/--setup S: statement to be executed once initially (default 'pass').
Execution time of this setup statement is NOT timed.
-p/--process: use time.process_time() (default is time.perf_counter())
-v/--verbose: print raw timing results; repeat for more digits precision
-u/--unit: set the output time unit (nsec, usec, msec, or sec)
-t/--target-time T: if --number is 0 the code will run until it
takes *at least* this many seconds
(default: 0.2)
-h/--help: print this usage message and exit
--: separate options from statement, use when statement starts with -
statement: statement to be timed (default 'pass')
A multi-line statement may be given by specifying each line as a
separate argument; indented lines are possible by enclosing an
argument in quotes and using leading spaces. Multiple -s options are
treated similarly.
If -n is not given, a suitable number of loops is calculated by trying
increasing numbers from the sequence 1, 2, 5, 10, 20, 50, ... until the
total time is at least --target-time seconds.
Note: there is a certain baseline overhead associated with executing a
pass statement. It differs between versions. The code here doesn't try
to hide it, but you should be aware of it. The baseline overhead can be
measured by invoking the program without arguments.
Classes:
Timer
@ -268,7 +236,7 @@ def main(args=None, *, _wrap_timer=None):
is not None, it must be a callable that accepts a timer function
and returns another timer function (used for unit testing).
"""
import getopt
import argparse
if args is None:
args = sys.argv[1:]
import _colorize
@ -276,54 +244,106 @@ def main(args=None, *, _wrap_timer=None):
theme = _colorize.get_theme(force_color=colorize).timeit
reset = theme.reset
try:
opts, args = getopt.getopt(args, "n:u:s:r:pt:vh",
["number=", "setup=", "repeat=",
"process", "target-time=",
"verbose", "unit=", "help"])
except getopt.error as err:
print(err)
print("use -h/--help for command line help")
return 2
epilog = """\
A multi-line statement may be given by specifying each line as a
separate argument; indented lines are possible by enclosing an
argument in quotes and using leading spaces. Multiple `-s` options are
treated similarly.
timer = default_timer
stmt = "\n".join(args) or "pass"
number = 0 # auto-determine
target_time = default_target_time
setup = []
repeat = default_repeat
verbose = 0
time_unit = None
If `-n` is not given, a suitable number of loops is calculated by trying
increasing numbers from the sequence 1, 2, 5, 10, 20, 50, ... until the
total time is at least `--target-time` seconds.
Note: there is a certain baseline overhead associated with executing a
pass statement. It differs between versions. The code here doesn't try
to hide it, but you should be aware of it. The baseline overhead can be
measured by invoking the program without arguments."""
parser = argparse.ArgumentParser(
prog="python -m timeit",
description="""\
Tool for measuring execution time of small code snippets.
This module avoids a number of common traps for measuring execution
times. See also Tim Peters' introduction to the Algorithms chapter in
the Python Cookbook, published by O'Reilly.
Library usage: see the Timer class.""",
epilog=epilog,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
"-n",
"--number",
type=int,
default=0,
help="how many times to execute 'statement' (default: see below)",
)
parser.add_argument(
"-r",
"--repeat",
type=int,
default=default_repeat,
help="how many times to repeat the timer (default %(default)s)",
)
parser.add_argument(
"-s",
"--setup",
action="append",
default=[],
help="statement to be executed once initially "
"(default 'pass'). Execution time of this "
"setup statement is NOT timed.",
)
parser.add_argument(
"-p",
"--process",
action="store_true",
help="use time.process_time() (default is time.perf_counter())",
)
parser.add_argument(
"-t",
"--target-time",
type=float,
default=default_target_time,
help="if --number is 0 the code will run until it takes "
"at least this many seconds (default %(default)s)",
)
parser.add_argument(
"-v",
"--verbose",
action="count",
default=0,
help="print raw timing results; repeat for more digits precision",
)
parser.add_argument(
"-u",
"--unit",
default=None,
choices=["nsec", "usec", "msec", "sec"],
help="set the output time unit",
)
parser.add_argument(
"statement",
nargs="*",
default=["pass"],
help="statement to be timed (default 'pass')",
)
try:
ns = parser.parse_args(args)
except SystemExit as e:
return e.code
timer = time.process_time if ns.process else default_timer
stmt = "\n".join(ns.statement) or "pass"
number = ns.number
target_time = ns.target_time
setup = "\n".join(ns.setup) or "pass"
repeat = max(ns.repeat, 1)
verbose = ns.verbose
time_unit = ns.unit
units = {"nsec": 1e-9, "usec": 1e-6, "msec": 1e-3, "sec": 1.0}
precision = 3
for o, a in opts:
if o in ("-n", "--number"):
number = int(a)
if o in ("-s", "--setup"):
setup.append(a)
if o in ("-u", "--unit"):
if a in units:
time_unit = a
else:
print("Unrecognized unit. Please select nsec, usec, msec, or sec.",
file=sys.stderr)
return 2
if o in ("-r", "--repeat"):
repeat = int(a)
if repeat <= 0:
repeat = 1
if o in ("-p", "--process"):
timer = time.process_time
if o in ("-t", "--target-time"):
target_time = float(a)
if o in ("-v", "--verbose"):
if verbose:
precision += 1
verbose += 1
if o in ("-h", "--help"):
print(__doc__, end="")
return 0
setup = "\n".join(setup) or "pass"
precision = 3 + max(verbose - 1, 0)
# Include the current directory, so that local imports work (sys.path
# contains the directory of this script, rather than the current

View file

@ -0,0 +1 @@
Use argparse for colour help timeit CLI. Patch by Hugo van Kemenade.