>>> [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将永远不会尝试评估其右侧。
首先,我们必须了解Python如何读取十六进制数字。
在tokenizer.c的巨大的tok_get
函数中,我们:
0x
。解析的标记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]
。
在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文件内,解析器会查看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;
0x
和 f
之间没有空格。 - Douglas其他答案已经解释清楚了具体发生了什么。但对我来说,有趣的是即使数字和运算符之间没有空格,Python 也能识别操作符。实际上,我的第一反应是“哇,Python 有一个奇怪的解析器”。
但在过于苛刻地判断之前,也许我应该问问我的其他朋友他们的看法:
Perl:
$ perl -le 'print(0xfor 3)'
15
Lua:
$ lua5.3 -e 'print(0xfor 4)'
15
Awk 没有 or
,但它有 in
:
$ awk 'BEGIN { a[15]=1; print(0x0fin a); }'
1
Ruby?(我不是很了解它,但让我们猜一下):
$ ruby -e 'puts 0x0for 5'
15
就算是紧贴在数字常量后面,Python并不是唯一一个能够识别字母运算符的脚本语言。
echo $(( 34#0xfor -15 ))
。然而,请注意,这与其他情况不同,因为这里没有隐藏的“or”运算符。 - inof就像其他人所解释的那样,它只是十六进制数字0xf
后面跟着运算符or
。运算符通常不需要周围有空格,除非必须避免歧义。在本例中,字母o
不能成为十六进制数的一部分,因此不存在歧义。请参阅Python语言参考文献中关于标记之间空格的章节。
由于短路计算,行的其余部分不会被计算,尽管当然会被解析和编译。
使用同样的“技巧”,您可以编写类似难以理解但不会抛出异常的Python代码,例如:
>>> 0xbin b'in'
False
>>> 0xbis 1000
False
>>> 0b1and 0b1is 0b00
False
>>> 0o1if 0b1else Oy1then
1
[0xf or x in (1, 2, 3)]
。你实际上已经发现了 Stack Overflow 语法高亮器中的一个小错误,因为它没有对0xfor
进行着色,而是跳过了or
;) - 1013或4
或“hello”和5
时给出了相同的结果。我怀疑这是为了适应二元运算符(如“3>4”)的情况,但在比较操作的情况下,它不是一个直接联系,因为你不能执行3and5
。我已经在Python开发者社区发布了帖子,看看他们会说什么。 - Stefano Borini"
和>
在标识符或其他形式的表达式中无效。令人意外的是,一串纯字母数字字符(即[a-z0-9]
)可以被解释为2个标记而不是一个“随机”的标记。 - GACy20