如何在不显着减速的情况下使用 Python 3.4 的枚举?

How to use Python 3.4's enums without significant slowdown?

我正在编写一个井字游戏并使用枚举来表示三个结果 -- losedrawwin。我认为这比使用字符串 ("lose", "win", "draw") 来指示这些值更好。但是使用枚举给我带来了显着的性能损失。

这是一个最小的例子,我只是引用 Result.lose 或文字字符串 lose.

import enum
import timeit
class Result(enum.Enum):
    lose = -1
    draw = 0
    win = 1

>>> timeit.timeit('Result.lose', 'from __main__ import Result')
1.705788521998329
>>> timeit.timeit('"lose"', 'from __main__ import Result')
0.024598151998361573

这比简单地引用全局变量要慢得多。

k = 12

>>> timeit.timeit('k', 'from __main__ import k')
0.02403248500195332

我的问题是:

您正在为计时循环计时。字符串文字本身 完全被忽略 :

>>> import dis
>>> def f(): "lose"
... 
>>> dis.dis(f)
  1           0 LOAD_CONST               1 (None)
              3 RETURN_VALUE        

这是一个什么都不做的函数。所以计时循环需要 0.024598151998361573 秒到 运行 100 万次。

在这种情况下,字符串实际上变成了 f 函数的文档字符串:

>>> f.__doc__
'lose'

但 CPython 通常会在代码中省略字符串文字(如果未分配)或表达式的其他部分:

>>> def f():
...     1 + 1
...     "win"
... 
>>> dis.dis(f)
  2           0 LOAD_CONST               2 (2)
              3 POP_TOP             

  3           4 LOAD_CONST               0 (None)
              7 RETURN_VALUE        

此处 1 + 1 折叠成常量 (2),字符串文字再次消失。

因此,您不能将其与查找 enum 对象的属性进行比较。是的,查找属性需要周期。但是查找另一个变量也是如此。如果你真的担心性能,你总是可以 cache 属性查找:

>>> import timeit
>>> import enum
>>> class Result(enum.Enum):
...     lose = -1
...     draw = 0
...     win = 1
... 
>>> timeit.timeit('outcome = Result.lose', 'from __main__ import Result')
1.2259576459764503
>>> timeit.timeit('outcome = lose', 'from __main__ import Result; lose = Result.lose')
0.024848614004440606

timeit 测试中,所有变量都是本地变量,因此 Resultlose 都是本地查找。

enum 属性查找确实比 'regular' 属性查找花费更多时间:

>>> class Foo: bar = 'baz'
... 
>>> timeit.timeit('outcome = Foo.bar', 'from __main__ import Foo')
0.04182224802207202

那是因为 enum 元 class 包含一个 specialised __getattr__ hook,每次查找属性时都会调用它; enum class 的属性在专门的字典中查找,而不是 class __dict__。执行该挂钩方法和额外的属性查找(以访问地图)都需要额外的时间:

>>> timeit.timeit('outcome = Result._member_map_["lose"]', 'from __main__ import Result')
0.25198313599685207
>>> timeit.timeit('outcome = map["lose"]', 'from __main__ import Result; map = Result._member_map_')
0.14024519600206986

在 Tic-Tac-Toe 游戏中,您通常不会担心归结为微不足道的时间差异。当 人类玩家 比您的计算机慢几个数量级时就不会了。人类玩家不会注意到 1.2 微秒或 0.024 微秒之间的差异。