如何在timeit.Timer中使用可调用对象作为setup?

2 投票
2 回答
2566 浏览
提问于 2025-04-15 22:37

我想给一些依赖于特定准备工作的代码计时。这个准备代码大概是这样的:

>>> b = range(1, 1001)

而我想要计时的代码大致是这样的:

>>> sorted(b)

不过我的代码用的是一个不同的函数,不用管这个。

总之,我知道只要把字符串传给timeit,就能给这段代码计时:

>>> import timeit
>>> t = timeit.Timer("sorted(b)", "b = range(1, 1001)")
>>> min(t.repeat(3, 100))

我该如何使用一个可调用的准备函数,让它把东西放到stmt可调用函数能访问的命名空间里呢?

换句话说,我想用可调用的函数来做上面的事情,而不是用包含可调用函数的字符串。

顺便提一下,我的最终目标是重用我单元测试中的代码来测量性能:

import unittest
class TestSorting(unittest.TestCase):

    def setUp(self):
        self.b = range(1, 1001)

    def test_sorted(self):
        sorted(self.b)

我预计需要做一点工作。timeit的准备部分需要创建一个TestSorting的实例,而stmt代码也得用到这个特定的实例。

一旦我明白如何让timeit的准备部分把东西放到和timeit stmt相同的命名空间里,我就会研究如何把unittest.TestCase的实例转换成可以直接用在timeit.Timer里的东西。

Matt

2 个回答

1

在2.6版本中,你可以“直接使用”,具体可以参考文档

在2.6版本中进行了更改:现在,stmtsetup这两个参数也可以接受不带参数的可调用对象。这意味着你可以把这些对象放进一个计时函数里,然后通过timeit()来执行它们。需要注意的是,由于多了一些函数调用,计时的开销会稍微大一些。

你是不是被迫使用旧版本的Python?如果是这样,我建议你可以把Python 2.6的timer.py源代码拿来,尝试“移植”到你正在使用的版本上——如果你的版本是2.5,这应该不难(当然,越往回移植就越困难)。我一般不推荐这样做用于“生产”代码,但对于支持测试、调试、性能测量等的代码来说,这样做是完全可以的。

1

我之前在这里问过类似的问题,也得到了一个没有解决我问题的答案。我觉得我们都没有用Python的方式清楚地表达出问题。

我不知道这算不算一种技巧、变通方法,或者这本来应该怎么做,但它确实有效:

>>> import timeit
>>> b = range(1, 1001)
>>> t = timeit.Timer('sorted(b)', setup='from __main__ import b')
>>> t.repeat(3, 100)
[0.0024309158325195312, 0.0024671554565429688, 0.0024020671844482422]
# was it really running 'sorted(b)'? let's compare'
>>> t = timeit.Timer('pass', setup='from __main__ import b')
>>> t.repeat(3, 100) 
[3.0994415283203125e-06, 2.1457672119140625e-06, 1.9073486328125e-06] 
# teeny times for 'pass', 'sorted(b)' took more time

我看过timeit.py这个文件,它的工作原理是构造一个语句,然后使用新的命名空间调用eval()。这些新的命名空间似乎和你的主命名空间没有任何联系。需要注意的是,因为sorted是一个内置函数,所以在timeit.repeat()中被eval的表达式可以访问到这个名字;如果是你自己定义的函数def,你就需要用from __main__ import b, myfunc来引入它。

我希望能有比这个更合适的方法来达到目的。

撰写回答