运行以下行时:
>>> [0xfor x in (1, 2, 3)]
我以为Python会返回一个错误.
相反,REPL返回:
[15]
原因可能是什么?
运行以下行时:
>>> [0xfor x in (1, 2, 3)]
我以为Python会返回一个错误.
相反,REPL返回:
[15]
原因可能是什么?
Python将表达式读取为[0xf or (x in (1, 2, 3))]
,因为:
多亏了short-circuit evaluation,它永远不会提升NameError
——如果or
操作符左边的表达式是真实值,Python永远不会try 计算它的右边.
首先,我们必须了解Python如何读取十六进制数.
在tokenizer.c的巨大tok_get
功能中,我们:
0x
.解析后的标记0xf
(因为"o"不在0-f的范围内)最终将被传递给PEG解析器,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
is lazy幸运的是,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]
.
在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;
} ...
然后,只要字符在0-f的范围内,它就会将数字的其余部分替换为parses:
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;