场景
让我们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.abc
class SortedList(collections.abc.Sequence):
,scraper.py
导入collections.abc
isinstance(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 行代码。