使用 Python 的三元运算符结合 lambda 的意外输出
Unexpected output using Pythons' ternary operator in combination with lambda
我有一个具体的情况想做以下事情(实际上比这个复杂,但我把问题归结为本质):
>>> (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
有趣的是,这是一个特例,因为如果我用“3”替换“1”,我会得到 expected/desired 结果:
>>> (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 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 了解问题跟踪。
我想我明白了为什么会出现错误,以及为什么您的重现是 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
是一个关键字,并且都是 lambda 加载常量。由于 1 == True
,代码对象非常相似,code_richcompare
中的所有检查都通过,并且代码对象比较相同。 (其中一项检查是针对行号的,因此该错误仅在 lambda 表达式位于同一行时出现。)
The bytecode compiler calls ADDOP_O(c, LOAD_CONST, (PyObject*)co, consts)
创建 LOAD_CONST
将 lambda 代码加载到堆栈的指令,并且 ADDOP_O
使用 dict 来跟踪它添加的对象,以试图保存space 关于重复常量之类的东西。它有一些处理来区分 0.0
、0
和 -0.0
之类的东西,否则它们会比较相等,但并不期望它们需要处理相等 - 但是 -不等价的代码对象。代码对象没有正确区分,两个 lambda 最终共享一个代码对象。
通过将 True
替换为 1.0
,我们可以重现 Python 2 上的错误:
>>> f1, f2 = lambda: 1, lambda: 1.0
>>> f2()
1
我没有 Python 3.5,所以我无法检查该错误是否仍然存在于该版本中。我在错误跟踪器中没有看到任何关于该错误的信息,但我可能只是错过了报告。如果错误仍然存在并且没有被报告,应该被报告。
我有一个具体的情况想做以下事情(实际上比这个复杂,但我把问题归结为本质):
>>> (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
有趣的是,这是一个特例,因为如果我用“3”替换“1”,我会得到 expected/desired 结果:
>>> (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 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 了解问题跟踪。
我想我明白了为什么会出现错误,以及为什么您的重现是 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
是一个关键字,并且都是 lambda 加载常量。由于 1 == True
,代码对象非常相似,code_richcompare
中的所有检查都通过,并且代码对象比较相同。 (其中一项检查是针对行号的,因此该错误仅在 lambda 表达式位于同一行时出现。)
The bytecode compiler calls ADDOP_O(c, LOAD_CONST, (PyObject*)co, consts)
创建 LOAD_CONST
将 lambda 代码加载到堆栈的指令,并且 ADDOP_O
使用 dict 来跟踪它添加的对象,以试图保存space 关于重复常量之类的东西。它有一些处理来区分 0.0
、0
和 -0.0
之类的东西,否则它们会比较相等,但并不期望它们需要处理相等 - 但是 -不等价的代码对象。代码对象没有正确区分,两个 lambda 最终共享一个代码对象。
通过将 True
替换为 1.0
,我们可以重现 Python 2 上的错误:
>>> f1, f2 = lambda: 1, lambda: 1.0
>>> f2()
1
我没有 Python 3.5,所以我无法检查该错误是否仍然存在于该版本中。我在错误跟踪器中没有看到任何关于该错误的信息,但我可能只是错过了报告。如果错误仍然存在并且没有被报告,应该被报告。