微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

为什么 Python 为 [0xfor x in (1, 2, 3)] 返回 [15]? TL;DR解析十六进制数运算符优先级or 很懒附录 A:PEG 解析器谢谢

如何解决为什么 Python 为 [0xfor x in (1, 2, 3)] 返回 [15]? TL;DR解析十六进制数运算符优先级or 很懒附录 A:PEG 解析器谢谢

运行以下行时:

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

我希望 Python 返回错误

相反,REPL 返回:

[15]

可能是什么原因?

解决方法

TL;DR

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

  1. Python tokenizer
  2. Operator precedence

由于 short-circuit evaluation,它永远不会引发 NameError - 如果 or 运算符左侧的表达式是一个真值,Python 永远不会尝试计算它的右侧。>

解析十六进制数

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

tokenizer.c 的巨大 tok_get 函数中,我们:

  1. Find 第一个 0x
  2. Keep reading the next characters 只要它们在 0-f 的范围内。

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

我们仍然需要解析其余的代码,or x in (1,3)],剩下的代码如下:

[15 or x in (1,3)]

运算符优先级

因为 inoperator precedenceor 高,我们可能希望 x in (1,3) 先求值。

这种情况很麻烦,因为 x 不存在并且会引发 NameError

or 很懒

幸运的是,Python 支持 Short-circuit evaluation,因为 or 是一个惰性操作符:如果左操作数等价于 True,Python 不会费心评估右操作数。

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

parsed = ast.parse('0xfor x in (1,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 中,解析器查看 one character after the 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;
            } ...

然后它 parses 剩下的数字,只要字符在 0-f 的范围内:

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

Eventually,它将指针设置为指向扫描的最后一个字符——最后一个十六进制字符后一个字符:

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

谢谢

,

其他答案已经说明了到底发生了什么。但对我来说,有趣的部分是即使数字和它之间没有空格,也能识别运算符。实际上,我的第一个想法是“哇,Python 有一个奇怪的解析器”。

但在做出过于严厉的判断之前,也许我应该问问其他朋友的看法:

Perl:

$ perl -le 'print(0xfor 3)'
15

路亚:

$ lua5.3 -e 'print(0xfor 4)'
15

Awk 没有 or,但它有 in

$ awk 'BEGIN { a[15]=1; print(0x0fin a); }'
1

红宝石? (我真的不知道,但让我们猜测一下):

$ ruby -e 'puts 0x0for 5'
15

是的,FWIW,Python 并不孤单,所有其他脚本类型的语言也能识别字母运算符,即使它立即粘在数字常量的后面。

,

正如其他人所解释的,它只是十六进制数 0xf 后跟运算符 or。操作员通常不需要周围的空间,除非有必要避免歧义。在这种情况下,字母 o 不能是十六进制数的一部分,因此没有歧义。请参阅 Python 语言参考中的 section on whitespace

由于短路评估,该行的其余部分没有被评估,当然,尽管它被解析和编译。

使用相同的“技巧”,您可以编写类似混淆的 Python 代码,不会引发异常,例如:

>>> 0xbin b'in'
False
>>> 0xbis 1000
False
>>> 0b1and 0b1is 0b00
False
>>> 0o1if 0b1else Oy1then
1

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。