【问题标题】:Strange behaviour using timeit + exec使用 timeit + exec 的奇怪行为
【发布时间】:2020-06-24 10:32:03
【问题描述】:

我在尝试对 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 也可以正常工作。

有人对这种奇怪的行为有解释吗?

【问题讨论】:

    标签: python python-3.7 timeit


    【解决方案1】:

    这实际上不仅限于timeitexec 的组合,而是单独exec 的问题:语句正在本地命名空间中执行,str.join 内部的生成器使用另一个(新)本地命名空间,之前设置的foo 是未知的。

    exec()eval() 的类定义块和参数在名称解析的上下文中是特殊的。类定义是可以使用和定义名称的可执行语句。这些引用遵循名称解析的正常规则,但在全局命名空间中查找未绑定的局部变量除外。类定义的命名空间成为类的属性字典。类块中定义的名称范围仅限于类块;它没有扩展到方法的代码块——这包括理解和生成器表达式,因为它们是使用函数范围实现的。这意味着以下将失败:

    class A:
        a = 42
        b = list(a + i for i in range(10))
    

    来源:https://docs.python.org/3/reference/executionmodel.html#resolution-of-names

    例如,另见list comprehension in exec with empty locals: NameError

    作为修复,您可以使用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 调用相同的错误。

    【讨论】:

    • 谢谢,很好的解释和修复工作。我认为与 timeit 的关系一定是 timeit 导致执行在类定义范围内发生?
    • 你说得对,我看错了。奇怪的是,尽管直接调用 exec(open('foobar.py').read()) 可以正常工作,没有问题。所以一定是timeit给整个执行上下文增加了some作用域,把foo的定义移到了全局作用域之外?
    猜你喜欢
    • 2011-07-31
    • 1970-01-01
    • 1970-01-01
    • 2014-04-19
    • 2018-02-13
    • 2014-08-30
    • 1970-01-01
    • 1970-01-01
    • 2019-12-04
    相关资源
    最近更新 更多