[Python-checkins] cpython: Issue6422 add autorange method to timeit.Timer

steven.daprano python-checkins at python.org
Sun Aug 14 11:27:20 EDT 2016


https://hg.python.org/cpython/rev/424eb46f7f3a
changeset:   102651:424eb46f7f3a
user:        Steven D'Aprano <steve at pearwood.info>
date:        Mon Aug 15 01:27:03 2016 +1000
summary:
  Issue6422 add autorange method to timeit.Timer

files:
  Doc/library/timeit.rst  |  19 ++++++++++++-
  Lib/test/test_timeit.py |  22 +++++++++++++++
  Lib/timeit.py           |  41 +++++++++++++++++++++-------
  3 files changed, 69 insertions(+), 13 deletions(-)


diff --git a/Doc/library/timeit.rst b/Doc/library/timeit.rst
--- a/Doc/library/timeit.rst
+++ b/Doc/library/timeit.rst
@@ -100,8 +100,8 @@
    can be controlled by passing a namespace to *globals*.
 
    To measure the execution time of the first statement, use the :meth:`.timeit`
-   method.  The :meth:`.repeat` method is a convenience to call :meth:`.timeit`
-   multiple times and return a list of results.
+   method.  The :meth:`.repeat` and :meth:`.autorange` methods are convenience
+   methods to call :meth:`.timeit` multiple times.
 
    The execution time of *setup* is excluded from the overall timed execution run.
 
@@ -134,6 +134,21 @@
             timeit.Timer('for i in range(10): oct(i)', 'gc.enable()').timeit()
 
 
+    .. method:: Timer.autorange(callback=None)
+
+       Automatically determine how many times to call :meth:`.timeit`.
+
+       This is a convenience function that calls :meth:`.timeit` repeatedly
+       so that the total time >= 0.2 second, returning the eventual
+       (number of loops, time taken for that number of loops). It calls
+       :meth:`.timeit` with *number* set to successive powers of ten (10,
+       100, 1000, ...) up to a maximum of one billion, until the time taken
+       is at least 0.2 second, or the maximum is reached.
+
+        If *callback* is given and is not *None*, it will be called after
+        each trial with two arguments: ``callback(number, time_taken)``.
+
+
    .. method:: Timer.repeat(repeat=3, number=1000000)
 
       Call :meth:`.timeit` a few times.
diff --git a/Lib/test/test_timeit.py b/Lib/test/test_timeit.py
--- a/Lib/test/test_timeit.py
+++ b/Lib/test/test_timeit.py
@@ -354,6 +354,28 @@
             s = self.run_main(switches=['-n1', '1/0'])
         self.assert_exc_string(error_stringio.getvalue(), 'ZeroDivisionError')
 
+    def autorange(self, callback=None):
+        timer = FakeTimer(seconds_per_increment=0.001)
+        t = timeit.Timer(stmt=self.fake_stmt, setup=self.fake_setup, timer=timer)
+        return t.autorange(callback)
+
+    def test_autorange(self):
+        num_loops, time_taken = self.autorange()
+        self.assertEqual(num_loops, 1000)
+        self.assertEqual(time_taken, 1.0)
+
+    def test_autorange_with_callback(self):
+        def callback(a, b):
+            print("{} {:.3f}".format(a, b))
+        with captured_stdout() as s:
+            num_loops, time_taken = self.autorange(callback)
+        self.assertEqual(num_loops, 1000)
+        self.assertEqual(time_taken, 1.0)
+        expected = ('10 0.010\n'
+                    '100 0.100\n'
+                    '1000 1.000\n')
+        self.assertEqual(s.getvalue(), expected)
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/Lib/timeit.py b/Lib/timeit.py
--- a/Lib/timeit.py
+++ b/Lib/timeit.py
@@ -207,6 +207,26 @@
             r.append(t)
         return r
 
+    def autorange(self, callback=None):
+        """Return the number of loops so that total time >= 0.2.
+
+        Calls the timeit method with *number* set to successive powers of
+        ten (10, 100, 1000, ...) up to a maximum of one billion, until
+        the time taken is at least 0.2 second, or the maximum is reached.
+        Returns ``(number, time_taken)``.
+
+        If *callback* is given and is not None, it will be called after
+        each trial with two arguments: ``callback(number, time_taken)``.
+        """
+        for i in range(1, 10):
+            number = 10**i
+            time_taken = self.timeit(number)
+            if callback:
+                callback(number, time_taken)
+            if time_taken >= 0.2:
+                break
+        return (number, time_taken)
+
 def timeit(stmt="pass", setup="pass", timer=default_timer,
            number=default_number, globals=None):
     """Convenience function to create Timer object and call timeit method."""
@@ -295,17 +315,16 @@
     t = Timer(stmt, setup, timer)
     if number == 0:
         # determine number so that 0.2 <= total time < 2.0
-        for i in range(1, 10):
-            number = 10**i
-            try:
-                x = t.timeit(number)
-            except:
-                t.print_exc()
-                return 1
-            if verbose:
-                print("%d loops -> %.*g secs" % (number, precision, x))
-            if x >= 0.2:
-                break
+        callback = None
+        if verbose:
+            def callback(number, time_taken):
+                msg = "{num} loops -> {secs:.{prec}g} secs"
+                print(msg.format(num=number, secs=time_taken, prec=precision))
+        try:
+            number, _ = t.autorange(callback)
+        except:
+            t.print_exc()
+            return 1
     try:
         r = t.repeat(repeat, number)
     except:

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list