Python 3 在编译时预先计算 2 ** 0.5 的值,因为此时两个操作数都是已知的。但是,sqrt 的值在编译时是不已知的,因此计算必然发生在运行时。
您没有计时计算 2 ** 0.5 需要多长时间,而只是计算加载常量所需的时间。
更公平的比较是
$ python3 -m timeit -s "from math import sqrt" "sqrt(2)"
5000000 loops, best of 5: 50.7 nsec per loop
$ python3 -m timeit -s "x = 2" "x**0.5"
5000000 loops, best of 5: 56.7 nsec per loop
我不确定是否有办法显示未优化的字节码。 Python 首先将源代码解析为抽象语法树 (AST):
>>> ast.dump(ast.parse("2**0.5"))
'Module(body=[Expr(value=BinOp(left=Num(n=2), op=Pow(), right=Num(n=0.5)))])'
更新:现在应用了这种特殊的优化directly to the abstract syntax tree,因此字节码是直接从类似的东西生成的
Module(body=Num(n= 1.4142135623730951))
ast 模块似乎没有应用优化。
编译器采用 AST 并生成未优化的字节码;在这种情况下,我相信它看起来会像(基于dis.dis("2**x") 和dis.dis("x**0.5") 的输出)
LOAD_CONST 0 (2)
LOAD_CONST 1 (0.5)
BINARY_POWER
RETURN_VALUE
原始字节码随后会被窥视孔优化器修改,这可以将这 4 条指令减少到 2 条,如 dis 模块所示。
然后编译器从 AST 生成字节码。
>>> dis.dis("2**0.5")
1 0 LOAD_CONST 0 (1.4142135623730951)
2 RETURN_VALUE
[虽然以下段落最初是考虑到优化字节码的想法而编写的,但其推理也适用于优化 AST。]
由于运行时不会影响两条LOAD_CONST 和后面的BINARY_POWER 指令的评估方式(例如,没有名称查找),因此窥孔优化器可以采用此字节码序列,执行@987654337 的计算@ 本身,并将前三个指令替换为单个 LOAD_CONST 指令,该指令会立即加载结果。