使用Pythons'意外输出三元运算符与lambda

时间:2015-12-05 02:56:00

标签: python boolean ternary-operator conditional-expressions

我有一个特定的情况,我想做以下事情(实际上它比这更多涉及,但我把问题简化为本质):

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

这是一种难以写的方式:

>>> 1 if True else 2
1

但实际上' 1',' True'和' 2'是评估的其​​他表达式,需要变量' e,我设置为' 0'这个简化的代码示例。

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

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

有趣的是,这是一个特例,因为如果我更换' 1'由' 3'我得到了预期/期望的结果:

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

如果我更换' 1'它甚至是正确的。 by' 0' (也可能是一个特例,因为1 == True和0 == False)

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

另外,如果我更换' 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

这种行为有什么解释?如何以一种很好的方式解决这种行为(避免使用'不是真的'或其他什么?

谢谢!

1 个答案:

答案 0 :(得分:10)

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

Code objects do equality comparisons by value,而不是指针,奇怪的是:

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是一个关键字,两个lambdas加载常量。从1 == True开始,代码对象与code_richcompare中的所有检查都足够相似,并且代码对象进行了比较。 (其中一项检查是针对行号,因此仅当lambda在同一行时才会出现错误。)

The bytecode compiler calls ADDOP_O(c, LOAD_CONST, (PyObject*)co, consts)创建LOAD_CONST指令,将lambda的代码加载到堆栈中,ADDOP_O使用dict跟踪它添加的对象,试图在复制常量之类的东西上节省空间。它有一些处理方法来区分0.00-0.0之类的内容,否则这些内容会相等,但是他们并不需要处理它们等等但不等价的代码对象。代码对象没有被正确区分,并且两个lambda最终共享一个代码对象。

True替换为1.0,我们可以重现Python 2上的错误:

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

我没有Python 3.5,所以我无法检查该版本中是否仍然存在该错误。我没有在bug跟踪器中看到有关该bug的任何内容,但我本可以错过报告。如果错误仍然存​​在且尚未报告,则应报告。