结合使用Python的三元运算符和lambda的意外输出 [英] Unexpected output using Pythons' ternary operator in combination with lambda

查看:123
本文介绍了结合使用Python的三元运算符和lambda的意外输出的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一种特定的情况,在这种情况下,我想执行以下操作(实际上,它涉及的范围更大,但我将问题归结为本质):

I have a specific situation in which I would like to do the following (actually it is more involved than this, but I reduced the problem to the essence):

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

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

which is a difficult way of writing:

>>> 1 if True else 2
1

但是实际上'1','True'和'2'是需要计算的附加表达式,它们需要变量'e',在此简化代码示例中,我将其设置为'0'.

but in reality '1','True' and '2' are additional expressions that get evaluated and which require the variable 'e', which I set to '0' for this simplified code example.

请注意,上述两个表达式在输出上的差异,

Note the difference in output from both expressions above, although

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

有趣的是,这是一个特例,因为如果我将"1"替换为"3",则会得到预期/期望的结果:

The funny thing is that this is a special case, because if I replace '1' by '3' I get the expected/desired result:

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

如果我将"1"替换为"0",这甚至是正确的(由于1 == True和0 == False,这也是一种特殊情况)

It's even correct if I replace '1' by '0' (which could also be a special case since 1==True and 0==False)

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

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

Also, if I replace 'True' by 'not False' or 'not not True', it still works:

>>> (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语句,并且不会产生错误:

Another alternative formulation uses the usual if..then..else statement and does not produce the error:

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

1

什么解释了此行为?如何以一种不错的方式解决此问题(避免使用"not not True"之类的东西?

What explains this behavior? How can I solve this behavior in a nice way (avoid to use 'not not True' or something?

谢谢!

推荐答案

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

I think I figured out why the bug is happening, and why your repro is Python 3 specific.

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

Code objects do equality comparisons by value, rather than by pointer, strangely enough:

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位于同一行时才会出现该错误.)

In Python 2, lambda e: True does a global name lookup and lambda e: 1 loads a constant 1, so the code objects for these functions don't compare equal. In Python 3, True is a keyword and both lambdas load constants. Since 1 == True, the code objects are sufficiently similar that all the checks in code_richcompare pass, and the code objects compare the same. (One of the checks is for line number, so the bug only appears when the lambdas are on the same line.)

字节码编译器将ADDOP_O(c, LOAD_CONST, (PyObject*)co, consts) 调用为创建LOAD_CONST指令以将lambda的代码加载到堆栈上,然后ADDOP_O使用dict跟踪其添加的对象,以尝试在诸如重复常量之类的内容上节省空间.它有一些处理方法可以区分0.00-0.0之类的东西,否则它们本来就比较相等,但是并不期望它们需要处理相等但不相等的代码对象.无法正确区分代码对象,两个lambda最终共享一个代码对象.

The bytecode compiler calls ADDOP_O(c, LOAD_CONST, (PyObject*)co, consts) to create the LOAD_CONST instruction that loads a lambda's code onto the stack, and ADDOP_O uses a dict to keep track of objects it's added, in an attempt to save space on stuff like duplicate constants. It has some handling to distinguish things like 0.0, 0, and -0.0 that would otherwise compare equal, but it wasn't expected that they'd ever need to handle equal-but-inequivalent code objects. The code objects aren't distinguished properly, and the two lambdas end up sharing a single code object.

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

By replacing True with 1.0, we can reproduce the bug on Python 2:

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

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

I don't have Python 3.5, so I can't check whether the bug is still present in that version. I didn't see anything in the bug tracker about the bug, but I could have just missed the report. If the bug is still there and hasn't been reported, it should be reported.

这篇关于结合使用Python的三元运算符和lambda的意外输出的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆