使用 Python 的三元运算符与 lambda 结合使用的意外输出



我有一个特定的情况,我想做以下事情(实际上它比这更复杂,但我把问题归结为本质):

>>> (lambda e: 1)(0) if (lambda e: True)(0) else (lambda e: 2)(0)
True

这是一种困难的写作方式:

>>> 1 if True else 2
1

但实际上,"1"、"True"one_answers"2"是额外的表达式,它们需要变量"e"进行求值,在这个简化的代码示例中,我将其设置为"0"。

注意上面两个表达式的输出差异,尽管

>>> (lambda e: 1)(0)
1
>>> (lambda e: True)(0)
True
>>> (lambda e: 2)(0)
2

有趣的是,这是一种特殊情况,因为如果我用"3"代替"1",我会得到预期/期望的结果:

>>> (lambda e: 3)(0) if (lambda e: True)(0) else (lambda e: 2)(0)
3

如果我用"0"替换"1"(这也可能是一种特殊情况,因为1==True和0==False)

>>> (lambda e: 0)(0) if (lambda e: True)(0) else (lambda e: 2)(0)
0

此外,如果我将"True"替换为"not False"或"not True",它仍然有效:

>>> (lambda e: 1)(0) if (lambda e: not False)(0) else (lambda e: 2)(0)
1
>>> (lambda e: 1)(0) if (lambda e: not not True)(0) else (lambda e: 2)(0)
1

另一种替代公式使用通常的if..then..else语句,不会产生错误:

>>> if (lambda e: True)(0):
    (lambda e: 1)(0)
else:
    (lambda e: 2)(0)
1

是什么解释了这种行为?我如何以一种好的方式解决这种行为(避免使用"not not True"或其他什么?

谢谢!

PS:这个问题揭示了Python中的一个错误,请参阅https://bugs.python.org/issue25843用于问题跟踪。

我想我已经弄清楚了为什么会发生错误,以及为什么您的repro是特定于Python 3的。

奇怪的是,代码对象通过值而不是指针进行相等比较:

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,因此这些函数的代码对象的比较不相等。在Python3中,True是一个关键字,两个lambdas都加载常量。由于1 == True,代码对象足够相似,使得code_richcompare中的所有检查都通过,并且代码对象比较相同。(其中一个检查是行号,所以只有当Lambda在同一行时才会出现错误。)

字节码编译器调用ADDOP_O(c, LOAD_CONST, (PyObject*)co, consts)来创建LOAD_CONST指令,该指令将lambda的代码加载到堆栈上,而ADDOP_O使用dict来跟踪它添加的对象,试图在重复常量等内容上节省空间。它有一些处理方法来区分0.00-0.0等本来可以比较相等的对象,但没想到它们需要处理相等但不相等的代码对象。代码对象没有正确区分,两个lambda最终共享一个代码对象。

通过用1.0替换True,我们可以在Python2:上重现错误

>>> f1, f2 = lambda: 1, lambda: 1.0
>>> f2()
1

我没有Python 3.5,所以我无法检查该版本中是否仍然存在错误。我在错误跟踪器中没有看到任何关于该错误的信息,但我可能只是错过了报告。如果错误仍然存在并且尚未报告,则应该报告它。

最新更新