首页 > 解决方案 > 为什么 Python 返回 [15] for [0xfor x in (1, 2, 3)]?

问题描述

运行以下行时:

>>> [0xfor x in (1, 2, 3)]

我希望 Python 返回一个错误。

相反,REPL 返回:

[15]

可能是什么原因?

标签: pythonpython-3.xoperator-precedenceshort-circuiting

解决方案


TL;博士

Python 将表达式读取为[0xf or (x in (1, 2, 3))],因为:

  1. Python 分词器。
  2. 运算符优先级

NameError由于短路评估,它永远不会引发- 如果留给or运算符的表达式是一个真实值,Python 将永远不会尝试评估它的右侧。

解析十六进制数

首先,我们要了解 Python 是如何读取十六进制数的。

tokenizer.c的巨大tok_get功能上,我们:

  1. 找到第一个0x
  2. 只要它们在 0-f 范围内,就继续阅读下一个字符。

解析后的标记0xf(因为“o”不在 0-f 范围内)最终将被传递给 PEG 解析器,后者会将其转换为十进制值15(参见附录 A)。

我们仍然需要解析其余的代码,or x in (1, 2, 3)],它与以下代码一样:

[15 or x in (1, 2, 3)]

运算符优先级

因为in具有比更高的运算符优先级or,我们可能希望x in (1, 2, 3)首先评估。

这是一个麻烦的情况,因为x不存在并且会引发NameError.

or懒惰

幸运的是,Python像惰性运算符一样支持短路求值or:如果左操作数等价于True,Python 不会费心求值右操作数。

我们可以使用ast模块看到它:

parsed = ast.parse('0xfor x in (1, 2, 3)', mode='eval')
ast.dump(parsed)

输出:


    Expression(
        body=BoolOp(
            op=Or(),
            values=[
                Constant(value=15),   # <-- Truthy value, so the next operand won't be evaluated.
                Compare(
                    left=Name(id='x', ctx=Load()),
                    ops=[In()],
                    comparators=[
                        Tuple(elts=[Constant(value=1), Constant(value=2), Constant(value=3)], ctx=Load())
                    ]
                )
            ]
        )
    )

所以最终表达式等于[15]


附录 A:PEG 解析器

pegen.cparsenumber_raw函数中,我们可以找到 Python 如何处理前导零:

    if (s[0] == '0') {
        x = (long)PyOS_strtoul(s, (char **)&end, 0);
        if (x < 0 && errno == 0) {
            return PyLong_FromString(s, (char **)0, 0);
        }
    }

PyOS_strtoulPython/mystrtoul.c.

在 mystrtoul.c 中,解析器查看0x. 如果它是一个十六进制字符,Python 将数字的基数设置为 16:

            if (*str == 'x' || *str == 'X') {
                /* there must be at least one digit after 0x */
                if (_PyLong_DigitValue[Py_CHARMASK(str[1])] >= 16) {
                    if (ptr)
                        *ptr = (char *)str;
                    return 0;
                }
                ++str;
                base = 16;
            } ...

然后,只要字符在 0-f 范围内,它就会解析其余的数字:

    while ((c = _PyLong_DigitValue[Py_CHARMASK(*str)]) < base) {
        if (ovlimit > 0) /* no overflow check required */
            result = result * base + c;
        ...
        ++str;
        --ovlimit;
    }

最终,它将指针设置为指向被扫描的最后一个字符 - 这是最后一个十六进制字符之后的一个字符:

    if (ptr)
        *ptr = (char *)str;

谢谢


推荐阅读