为啥 Python 返回 [15] for [0xfor x in (1, 2, 3)]? [复制]

Posted

技术标签:

【中文标题】为啥 Python 返回 [15] for [0xfor x in (1, 2, 3)]? [复制]【英文标题】:Why does Python return [15] for [0xfor x in (1, 2, 3)]? [duplicate]为什么 Python 返回 [15] for [0xfor x in (1, 2, 3)]? [复制] 【发布时间】:2021-07-08 23:45:07 【问题描述】:

运行以下行时:

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

我预计 Python 会返回错误。

相反,REPL 返回:

[15]

可能是什么原因?

【问题讨论】:

请注意,Python 将其视为[0xf or x in (1, 2, 3)]。实际上,您在 Stack Overflow 的语法高亮显示中发现了一个小错误,因为它呈现 0xfor 而不给 or 着色;) 非常出乎意料...显然这对打代码很有用,但感觉与其余语法完全不符。恕我直言,如果连续的字母数字字符串始终被视为单个标记,我会更喜欢。 我觉得这是解析器中的一个错误。作为记录,它与3or 4"hello"and 5 给出相同的结果。我怀疑这是为诸如“3> 4”之类的二元运算符提供情况的结果,但在比较操作的情况下,它不是直接连接,因为你不能这样做3and5。我在 python-dev 上发帖,看看他们怎么说 Storchaka 逐字“它不与规范相矛盾,但看起来很混乱,因此我们可能会更改规范和实现以防止混淆。”。自 2018 年以来,它也为人所知。 @StefanoBorini "hello"and 53>5 是不同的。 "> 在标识符或其他形式的表达式中无效。出乎意料的是,一串纯字母数字字符(即[a-z0-9])可以被解释为2个标记而不是一个“随机” 【参考方案1】:

TL;DR

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

    Python tokenizer. Operator precedence。

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

解析十六进制数

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

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

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

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

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

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

运算符优先级

因为in 的operator precedence 比or 高,我们可能期望x in (1, 2, 3) 先评估。

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

or 很懒

幸运的是,Python 支持Short-circuit evaluation,因为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.c 的parsenumber_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;

谢谢

CSI_Tech_Dept 来自 reddit,将我引至 tokenizer.c 文件中的正确部分。 The original Tweet。

【讨论】:

有时我认为 Python 从未打算成为真正的产品。 670 行标记化在一个方法中?谁愿意维护它? @defalt 你在说什么空间?在被询问的行中,0xf 之间没有空格。 @ThomasWeller A) 对于分词器来说,这还不错。 B) Python 不是一种“产品”,无论是真实的还是其他的,并且确实不是一个产品。它最初是一种教学语言。【参考方案2】:

其他答案已经说明了到底发生了什么。但对我来说,有趣的部分是即使数字和它之间没有空格,也能识别运算符。实际上,我的第一个想法是“哇,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 并不孤单,所有其他脚本类型的语言也能识别字母运算符,即使紧跟在数字常量的后面。

【讨论】:

如果你使用 bash 或 zsh,你也可以试试这个:echo $(( 34#0xfor -15 )) ― 不过,这与其他情况不同,因为这里没有隐藏的or 运算符。【参考方案3】:

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

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

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

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

【讨论】:

以上是关于为啥 Python 返回 [15] for [0xfor x in (1, 2, 3)]? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

为啥OpenCV for Python的cv2.HoughLines的返回值需要用索引来访问?

为啥 parseInt("0x") 返回 NaN [重复]

Flash与S3C44B0X连接时地址线为啥要偏移一位

为啥 python datetime replace timezone 返回不同的时区?

为啥这个逻辑/按位运算返回 1?

Python3---内置函数---str()