在 timeit.repeat 中重置全局变量



场景

让我们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的值而有所不同:我预计,对于每次重复,setup参数中的import test语句将重新运行test中的primes = []语句,从而"重置"primes,以便每次重复执行的代码都是相同的。但是,相反,使用由先前执行产生的primes值,因此我不得不将语句test.primes = []添加到setup参数中:

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

问题

这让我想到了一个问题:是否有一种直接的方法(即在一个语句中(将所有全局变量的值"重置"为它们首次在模块中分配时的值?

在此特定场景中,添加一个语句以手动"重置"primes工作正常,但请考虑存在大量全局变量的情况,并且您希望"重置"所有全局变量。


支线任务

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

让我们从你的侧面问题开始,因为事实证明它实际上是一切的核心:

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

因为,正如导入系统和import语句的文档中所解释的那样,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.py导入collections.abcclass SortedList(collections.abc.Sequence):scraper.py导入collections.abcisinstance(something, collections.abc.Sequence),你会希望SortedList通过该测试——但如果它们是两个完全独立的类型,因为它们来自两个不同的模块对象,恰好具有相同的名称,

  • 如果你有 12 个模块都import pandas as pd,你将运行所有 Pandas 初始化代码 12 次。除了你的一些模块也可能相互导入,所以它们每个都会运行多次,每次都导入 Pandas。您认为运行所有 Pandas 初始化 60 次需要多长时间?


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

当你不这样做时,这通常表明你的设计有问题(这里很可能就是这种情况(。

但"几乎总是"并不是"总是"。所以有办法解决它。对于实时代码来说,它们通常都不是一个好主意,但是对于单元测试和基准测试之类的东西,有三个基本选项都可以,只要权衡是你想要的:

  • del sys.modules['test'].这显然是相当笨拙的,但它实际上正是你想要的。对旧模块的任何现有引用都是完全不变的,但是下次有人import test时,他们将获得一个全新的test模块。
  • importlib.reload(test).这听起来不错,但一方面可能是矫枉过正(请注意,它强制重新编译模块源代码,而您不需要(,而另一方面它可能还不够(它实际上并没有重置全局变量——如果你的代码确实在顶层primes = [],那一行就会被执行,所以谁在乎呢, 但是,如果你的代码确实globals().setdefault('primes', [])pi函数内部,你就在乎(。
  • 而不是import test,手动执行执行模块的所有步骤(请参阅importlib文档中的示例(,但不要将其存储在sys.modules['test']test中,只需将其存储在每次测试后丢弃的局部变量中即可。这可能是最干净的,尽管它确实意味着 6 行代码而不是 1 行代码。

相关内容

  • 没有找到相关文章

最新更新