在timeit.repeat中重置全局变量

时间:2018-08-27 20:49:35

标签: python timing timeit

场景

test作为我们以__main__运行的模块。该模块包含一个名为primes的全局变量,该变量在模块中使用以下分配进行了初始化。

primes = []

该模块还包含一个名为pi的函数,该函数会更改此全局变量:

def pi(n):
    global primes
    """Some code that modifies the global 'primes' variable"""

然后我想使用内置的timeit模块来计时所述功能。我想使用timeit.repeat函数并获取计时的最小值,以提高测量精度(而不是只测量一次,因为不相关的过程可能会降低速度)。

print(min(timeit.repeat('test.pi(50000)',
                        setup="import test",
                        number=1, repeat=10)) * 1000)

问题在于pi函数的行为取决于primes的值:我希望对于每次重复,import test参数中的setup语句会在primes = []中重新运行test语句,从而“重置” primes,以使每次重复执行的代码都相同。但是,取而代之的是,使用了前一次执行产生的primes的值,因此我不得不在test.primes = []参数中添加语句setup

print(min(timeit.repeat('test.pi(50000)',
                        setup="import test \n" + "test.primes = []",
                        number=1, repeat=10)) * 1000)


问题

这引出我一个问题:是否有一种直接方法(即,在一条语句中)将 all 全局变量的值“重置”为在变量中首次分配时的值模块?

在这种特定情况下,添加一个语句以手动“重置” primes可以很好地工作,但是考虑到存在很多全局变量,而您想“重置”所有这些变量的情况。 / p>


侧问离子

为什么语句import test不重新运行初始的primes = []分配?

1 个答案:

答案 0 :(得分:1)

让我们从您的附带问题开始,因为事实证明,它实际上是一切的中心:

  

为什么语句import test不重新运行初始的primes = []分配?”

因为,正如the import systemthe import statement上的文档中所述,import test的作用是松散地使用了以下伪代码:

if 'test' not in sys.modules:
    find, load (compiling if needed), and exec the module
    sys.modules['test'] = result
test = sys['test.modules']

好,但是为什么这样做?

  • 如果您有两个都导入相同模块的模块,则它们希望看到相同的全局变量。请记住,在函数顶层定义的类型,函数等都是全局变量。例如,如果sortedlist.pycollections.abc导入class SortedList(collections.abc.Sequence):,而scraper.py则将collections.abc导入isinstance(something, collections.abc.Sequence),则您需要一个{{1} }以通过该测试-但是,如果它们是两个完全独立的类型,则不会,因为它们来自恰好具有相同名称的两个不同的模块对象

  • 如果所有SortedList都具有12个模块,则将运行所有Pandas初始化代码12次。除了您的某些模块可能还会相互导入之外,因此它们将分别运行多次,并每次都导入Pandas。您认为运行所有Pandas初始化60次需要多长时间?


因此,重复使用现有模块几乎总是您想要的。

如果您不这样做,通常表明您的设计存在问题(这里可能就是这种情况)。

但是“几乎总是”不是“总是”。因此,有很多解决方法。对于实时代码,通常都不是一个好主意,但是对于单元测试和基准测试而言,只要权衡取舍就可以使用三个基本选项:

  • del sys.modules['test']。这显然是很棘手的,但实际上确实可以满足您在这里想要的目的。现有的对旧模块的引用完全没有变化,但是下一次有人import pandas as pd时,他们将获得一个全新的import test模块。
  • importlib.reload(test)。这听起来不错,但一方面可能会过大(请注意,它会强制重新编译模块源,而您不需要),而另一方面,这可能还不够(实际上并不会重置全局变量-如果您的代码在最高级别执行了test,则该行将被执行,所以谁在乎,但是如果您的代码却在primes = []函数中执行了globals().setdefault('primes', []),则您关心)。
  • 代替执行pi,手动执行执行模块的所有步骤(请参阅import test文档中的examples),但不要将其存储在{{1}中}或importlib中,只需将其存储在每次测试后丢弃的局部变量中即可。这可能是最干净的,尽管它的确意味着6行代码而不是1行。