Inf*_*ity 64 python operator-precedence short-circuiting python-3.x
运行以下行时:
>>> [0xfor x in (1, 2, 3)]
Run Code Online (Sandbox Code Playgroud)
我希望 Python 返回一个错误。
相反,REPL 返回:
[15]
可能是什么原因?
Inf*_*ity 99
Python 将表达式读作[0xf or (x in (1, 2, 3))]
,因为:
NameError
由于短路评估,它永远不会引发- 如果or
运算符左侧的表达式是一个真值,Python 将永远不会尝试评估它的右侧。
首先,我们要了解Python是如何读取十六进制数的。
在tokenizer.c的巨大tok_get
功能上,我们:
解析的标记0xf
(因为“o”不在 0-f 的范围内),最终将传递给 PEG 解析器,该解析器会将其转换为十进制值15
(参见附录 A)。
我们仍然需要解析其余的代码,or x in (1, 2, 3)]
,剩下的代码如下:
[15 or x in (1, 2, 3)]
Run Code Online (Sandbox Code Playgroud)
因为in
具有比更高的运算符优先级or
,我们可能期望x in (1, 2, 3)
首先评估。
这是一个麻烦的情况,因为x
不存在并且会引发NameError
.
or
很懒幸运的是,Python 支持短路求值,就像or
一个惰性运算符:如果左操作数等价于True
,Python 不会费心求值右操作数。
我们可以使用ast
模块看到它:
[15 or x in (1, 2, 3)]
Run Code Online (Sandbox Code Playgroud)
输出:
parsed = ast.parse('0xfor x in (1, 2, 3)', mode='eval')
ast.dump(parsed)
Run Code Online (Sandbox Code Playgroud)
所以最终的表达式等于[15]
。
在pegen.c的parsenumber_raw
函数中,我们可以找到 Python 如何处理前导零:
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())
]
)
]
)
)
Run Code Online (Sandbox Code Playgroud)
PyOS_strtoul
在Python/mystrtoul.c
.
在 mystrtoul.c 中,解析器查看 .c之后的一个字符0x
。如果是十六进制字符,Python 将数字的基数设置为 16:
if (s[0] == '0') {
x = (long)PyOS_strtoul(s, (char **)&end, 0);
if (x < 0 && errno == 0) {
return PyLong_FromString(s, (char **)0, 0);
}
}
Run Code Online (Sandbox Code Playgroud)
然后,只要字符在 0-f 范围内,它就会解析数字的其余部分:
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;
} ...
Run Code Online (Sandbox Code Playgroud)
最终,它将指针设置为指向扫描的最后一个字符——这是最后一个十六进制字符后的一个字符:
if (ptr)
*ptr = (char *)str;
Run Code Online (Sandbox Code Playgroud)
ilk*_*chu 26
其他答案已经说明究竟发生了什么。但对我来说,有趣的部分是即使数字和它之间没有空格,也能识别运算符。实际上,我的第一个想法是“哇,Python 有一个奇怪的解析器”。
但在过于苛刻之前,也许我应该问问其他朋友的看法:
珀尔:
$ perl -le 'print(0xfor 3)'
15
Run Code Online (Sandbox Code Playgroud)
路亚:
$ lua5.3 -e 'print(0xfor 4)'
15
Run Code Online (Sandbox Code Playgroud)
Awk 没有or
,但它有in
:
$ awk 'BEGIN { a[15]=1; print(0x0fin a); }'
1
Run Code Online (Sandbox Code Playgroud)
红宝石?(我真的不知道,但让我们猜猜):
$ ruby -e 'puts 0x0for 5'
15
Run Code Online (Sandbox Code Playgroud)
是的,FWIW,Python 并不孤单,所有其他脚本类型的语言也能识别字母运算符,即使立即卡在数字常量的后面。
小智 5
正如其他人所解释的那样,它只是0xf
后跟操作符的十六进制数or
。操作员通常不需要周围的空间,除非有必要避免歧义。在这种情况下,字母o
不能是十六进制数的一部分,因此没有歧义。请参阅Python 语言参考中有关空格的部分。
由于短路评估,该行的其余部分没有被评估,当然,尽管它被解析和编译。
使用相同的“技巧”,您可以编写类似的混淆 Python 代码,不会抛出异常,例如:
>>> 0xbin b'in'
False
>>> 0xbis 1000
False
>>> 0b1and 0b1is 0b00
False
>>> 0o1if 0b1else Oy1then
1
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
8189 次 |
最近记录: |