我编写了以下函数:
def f():
for i in range(100000):
print(i)
some_function_that_doesnt_exist()
运行文件时,这将打印出100000范围内的数字,然后引发错误:NameError: name 'some_function_that_doesnt_exist' is not defined
。
这是一个有关Python的更广泛问题的示例。如果在Python VM解释(或通过JIT对其进行解释)之前将Python代码编译为字节码,为什么在编译过程中没有看到此错误?为什么在最终看到错误之前打印出100000个数字?在像C这样的纯编译语言中,我们会在编译时看到错误,那么为什么我们不能在这种“部分编译”语言中看到它?
答案 0 :(得分:3)
您可以使用dis
查看python VM的运行情况:
import dis
def f():
for i in range(100000):
print(i)
some_function_that_doesnt_exist()
dis.dis(f)
将输出:
4 0 SETUP_LOOP 24 (to 26)
2 LOAD_GLOBAL 0 (range)
4 LOAD_CONST 1 (100000)
6 CALL_FUNCTION 1
8 GET_ITER
>> 10 FOR_ITER 12 (to 24)
12 STORE_FAST 0 (i)
5 14 LOAD_GLOBAL 1 (print)
16 LOAD_FAST 0 (i)
18 CALL_FUNCTION 1
20 POP_TOP
22 JUMP_ABSOLUTE 10
>> 24 POP_BLOCK
6 >> 26 LOAD_GLOBAL 2 (some_function_that_doesnt_exist)
28 CALL_FUNCTION 0
30 POP_TOP
32 LOAD_CONST 0 (None)
34 RETURN_VALUE
这是python VM实际运行的内容。从第10到24行可以看到,它实际上是处理打印的。
仅当到达第26行时,python VM才会尝试加载该函数,这会导致名称错误。
稍有不同,当编译语言将代码编译为字节码(例如,从C到汇编)时,函数调用需要确切知道函数在内存中的位置。因此,当您尝试编译不存在的函数时,它将无法将其转换为字节码,因为它将无法查找函数位置。
Python VM可以访问可以实时修改的全局数组,并且在该数组中的查找也是实时的。
正在执行:
dis.dis("def some_function_that_doesnt_exist(): 1")
将导致:
1 0 LOAD_CONST 0 (<code object some_function_that_doesnt_exist at 0x7f38dc90ed20, file "<dis>", line 1>)
2 LOAD_CONST 1 ('some_function_that_doesnt_exist')
4 MAKE_FUNCTION 0
6 STORE_NAME 0 (some_function_that_doesnt_exist)
8 LOAD_CONST 2 (None)
10 RETURN_VALUE
第2行从const表中加载函数名称,然后在第4行创建函数后,根据名称在第6行存储它。因此,当下次查找该函数时,python VM将能够找到它。
答案 1 :(得分:1)
Python代码被编译为字节码,然后在运行时解释该字节码。您的代码是完全有效的Python,因此它将成功编译为字节码。但是,当解释程序尝试执行该字节码时,它将在运行时引发错误。