我想我明白了为什么会发生这个错误,以及为什么你的重现是 Python 3 特定的。
代码对象按值进行相等比较 https://hg.python.org/cpython/file/3.5/Objects/codeobject.c#l535,而不是通过指针,奇怪的是:
static PyObject *
code_richcompare(PyObject *self, PyObject *other, int op)
{
...
co = (PyCodeObject *)self;
cp = (PyCodeObject *)other;
eq = PyObject_RichCompareBool(co->co_name, cp->co_name, Py_EQ);
if (eq <= 0) goto unequal;
eq = co->co_argcount == cp->co_argcount;
if (!eq) goto unequal;
eq = co->co_kwonlyargcount == cp->co_kwonlyargcount;
if (!eq) goto unequal;
eq = co->co_nlocals == cp->co_nlocals;
if (!eq) goto unequal;
eq = co->co_flags == cp->co_flags;
if (!eq) goto unequal;
eq = co->co_firstlineno == cp->co_firstlineno;
if (!eq) goto unequal;
...
在Python 2中,lambda e: True
进行全局名称查找并lambda e: 1
加载一个常量1
,因此这些函数的代码对象比较不相等。在Python 3中,True
是一个关键字,两个 lambda 都加载常量。自从1 == True
,代码对象足够相似,所有检查code_richcompare
通过,代码对象比较相同。 (其中一项检查是针对行号,因此仅当 lambda 位于同一行时才会出现该错误。)
字节码编译器调用ADDOP_O(c, LOAD_CONST, (PyObject*)co, consts) https://hg.python.org/cpython/file/3.5/Python/compile.c#l1473来创建LOAD_CONST
将 lambda 代码加载到堆栈上的指令,以及ADDOP_O
使用字典来跟踪它添加的对象,试图节省重复常量等内容的空间。它有一些处理来区分诸如0.0
, 0
, and -0.0
否则会比较相等,但并不期望它们需要处理相等但不等价的代码对象。代码对象没有正确区分,两个 lambda 最终共享一个代码对象。
通过替换True
with 1.0
,我们可以在 Python 2 上重现该错误:
>>> f1, f2 = lambda: 1, lambda: 1.0
>>> f2()
1
我没有Python 3.5,所以我无法检查该版本中是否仍然存在该错误。我没有在错误跟踪器中看到任何有关该错误的信息,但我可能只是错过了该报告。如果错误仍然存在并且尚未报告,则应该报告。