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

Posted

技术标签:

【中文标题】使用 Python 的三元运算符与 lambda 组合的意外输出【英文标题】:Unexpected output using Pythons' ternary operator in combination with lambda 【发布时间】:2016-03-10 02:49:01 【问题描述】:

我有一个具体的情况,我想做以下事情(其实比这涉及的多,但我把问题归结为本质):

>>> (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' 替换为 '0' 甚至是正确的(这也可能是一种特殊情况,因为 1==True 和 0==False)

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

另外,如果我将 'True' 替换为 'not False' 或 'not 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 以了解问题跟踪。

【问题讨论】:

(lambda e: 1) 只是一个在调用时总是返回1 的函数。 (lambda e: 3) 返回 3。我没有看到任何奇怪或特殊情况。你能澄清一下问题是什么吗?我知道这是一个玩具示例,但没有理由做这样的 lambda... lambda 只是一个可以直接添加到 if 的表达式。 我正在阅读一本关于编程语言的书。我正在编写一个软件,它将以字符串形式输出 Python 代码。所以基本上,我展示的这段代码放在“”中并分配给一个变量。这应该是递归的,因此值 '1'、'True' 和 '0' 通常可以是其他被评估的表达式。在某些情况下,表达式取决于变量“e”,因此它通常作为参数传递。因此我使用(lambda e: 1)(0) 而不是1,因为通常不知道输出。我希望这能让它更清楚...... 请注意,lambda 表达式会立即被计算,所以(lambda e: 1)(0) 而不是(lambda e: 1) 有趣的是,(lambda e: 1)(0) if (lambda e: False)(0) else (lambda e: 1)(0)(意味着 else 子句运行)返回 1,而不是 True Something is very wrong with Python 3's behavior here. - 编辑:Ideone 出了点问题,因为该链接中的输出与该版本的代码不对应。 【参考方案1】:

我想我明白了为什么会出现这个错误,以及为什么你的 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 这样的东西,否则它们会比较相等,但预计它们不需要处理相等但不相等的代码对象。代码对象没有被正确区分,两个 lambdas 最终共享一个代码对象。

通过将True 替换为1.0,我们可以在Python 2 上重现该错误:

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

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

【讨论】:

在 3.5 中还是一样的。看起来像是一个错误报告的候选者。 @user2357112 :这如何解释我的问题中观察到的行为?你说lambda e:1 加载了一个常量1,但是为什么这个表达式会返回True?谢谢。 @tvo:它的代码对象与lambda e: True 的比较相等,编译器最终将lambda e: True 的代码对象重用于lambda e: 1,而实际上它不应该这样做。 @user2357112:嗨,我使用通常的 if 语句在问题中添加了一个额外的代码示例。这并没有说明问题。这和你的解释一致吗? @user2357112:我刚刚检查过:(lambda e: False)(lambda e: 0) 之间发生了同样的冲突,这支持了你的答案。你报告了错误吗?

以上是关于使用 Python 的三元运算符与 lambda 组合的意外输出的主要内容,如果未能解决你的问题,请参考以下文章

python 三元运算与lambda

Python基础-lambda表达式与三元运算

python中的lambda表达式与三元运算

Python-三元运算符和lambda表达式

Python 函数进阶-lambda匿名函数和三元运算符

Python:拥有三元运算符的有效方法[重复]