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_strtoul 在Python/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;
谢谢