使用 timeit + exec 的奇怪行为
Strange behaviour using timeit + exec
我在尝试为 python 脚本计时时遇到了一些奇怪的行为。最小示例:
foobar.py:
foo = 'Hello'
print(''.join(c for c in foo if c not in 'World'))
print(''.join(c for c in 'World' if c not in foo))
timer.py:
import timeit
timeit.repeat(stmt="exec(open('foobar.py').read())", repeat=1, number=1)
当我 运行 foobar.py 时,我得到了预期的输出:
> python3 foobar.py
He
Wrd
但是,当我 运行 timer.py 时,出现以下错误:
> python3 timer.py
He
Traceback (most recent call last):
File "timer.py", line 2, in <module>
timeit.repeat(stmt="exec(open('foobar.py').read())", repeat=1, number=1)
File "/usr/lib/python3.7/timeit.py", line 237, in repeat
return Timer(stmt, setup, timer, globals).repeat(repeat, number)
File "/usr/lib/python3.7/timeit.py", line 204, in repeat
t = self.timeit(number)
File "/usr/lib/python3.7/timeit.py", line 176, in timeit
timing = self.inner(it, self.timer)
File "<timeit-src>", line 6, in inner
File "<string>", line 3, in <module>
File "<string>", line 3, in <genexpr>
NameError: name 'foo' is not defined
也许最奇怪的是 foobar.py 中的第一个打印语句工作正常,而第二个却不行。在没有 timeit 包装器的情况下使用 exec 执行 foobar.py 也可以正常工作。
谁能解释这种奇怪的行为?
这实际上不限于 timeit
与 exec
的结合,而是 exec
单独的问题:语句在本地命名空间中执行,生成器在 str.join
使用另一个(新的)本地命名空间,其中先前设置的 foo
未知。
Class definition blocks and arguments to exec()
and eval()
are special in the context of name resolution. A class definition is an executable statement that may use and define names. These references follow the normal rules for name resolution with an exception that unbound local variables are looked up in the global namespace. The namespace of the class definition becomes the attribute dictionary of the class. The scope of names defined in a class block is limited to the class block; it does not extend to the code blocks of methods – this includes comprehensions and generator expressions since they are implemented using a function scope. This means that the following will fail:
class A:
a = 42
b = list(a + i for i in range(10))
来源:https://docs.python.org/3/reference/executionmodel.html#resolution-of-names
另见 示例。
作为修复,您可以使用 exec
的第二个参数设置全局字典,因此所有语句都使用相同的字典:
timeit.repeat(stmt="exec(open('foobar.py').read(), locals())", repeat=1, number=1)
或者您可以完全删除 exec
并使用 import
:
timeit.repeat(stmt="import foobar", repeat=1, number=1)
顺便说一下,直接执行 exec(open('foobar.py').read()
也仅当您在全局(模块)范围内时才有效。如果您只是将它放在一个函数中,它将停止工作并显示与 timeit
.
调用时相同的错误
我在尝试为 python 脚本计时时遇到了一些奇怪的行为。最小示例:
foobar.py:
foo = 'Hello'
print(''.join(c for c in foo if c not in 'World'))
print(''.join(c for c in 'World' if c not in foo))
timer.py:
import timeit
timeit.repeat(stmt="exec(open('foobar.py').read())", repeat=1, number=1)
当我 运行 foobar.py 时,我得到了预期的输出:
> python3 foobar.py
He
Wrd
但是,当我 运行 timer.py 时,出现以下错误:
> python3 timer.py
He
Traceback (most recent call last):
File "timer.py", line 2, in <module>
timeit.repeat(stmt="exec(open('foobar.py').read())", repeat=1, number=1)
File "/usr/lib/python3.7/timeit.py", line 237, in repeat
return Timer(stmt, setup, timer, globals).repeat(repeat, number)
File "/usr/lib/python3.7/timeit.py", line 204, in repeat
t = self.timeit(number)
File "/usr/lib/python3.7/timeit.py", line 176, in timeit
timing = self.inner(it, self.timer)
File "<timeit-src>", line 6, in inner
File "<string>", line 3, in <module>
File "<string>", line 3, in <genexpr>
NameError: name 'foo' is not defined
也许最奇怪的是 foobar.py 中的第一个打印语句工作正常,而第二个却不行。在没有 timeit 包装器的情况下使用 exec 执行 foobar.py 也可以正常工作。
谁能解释这种奇怪的行为?
这实际上不限于 timeit
与 exec
的结合,而是 exec
单独的问题:语句在本地命名空间中执行,生成器在 str.join
使用另一个(新的)本地命名空间,其中先前设置的 foo
未知。
Class definition blocks and arguments to
exec()
andeval()
are special in the context of name resolution. A class definition is an executable statement that may use and define names. These references follow the normal rules for name resolution with an exception that unbound local variables are looked up in the global namespace. The namespace of the class definition becomes the attribute dictionary of the class. The scope of names defined in a class block is limited to the class block; it does not extend to the code blocks of methods – this includes comprehensions and generator expressions since they are implemented using a function scope. This means that the following will fail:class A: a = 42 b = list(a + i for i in range(10))
来源:https://docs.python.org/3/reference/executionmodel.html#resolution-of-names
另见
作为修复,您可以使用 exec
的第二个参数设置全局字典,因此所有语句都使用相同的字典:
timeit.repeat(stmt="exec(open('foobar.py').read(), locals())", repeat=1, number=1)
或者您可以完全删除 exec
并使用 import
:
timeit.repeat(stmt="import foobar", repeat=1, number=1)
顺便说一下,直接执行 exec(open('foobar.py').read()
也仅当您在全局(模块)范围内时才有效。如果您只是将它放在一个函数中,它将停止工作并显示与 timeit
.