如果 Python 源代码在 interpretation/JITing 之前被编译为字节码,为什么这个错误没有在运行前被捕获?
If Python source code is compiled to bytecode before interpretation/JITing, why isn't this error caught before runtime?
我写了下面的函数:
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 代码在 Python VM 解释(或 JIT)之前被编译为字节码,为什么我们在编译期间看不到这个错误?为什么在我们最终看到错误之前打印了 100000 个数字?在像 C 这样的纯编译语言中,我们会在编译时看到错误,那么为什么我们在这种 "partially compiled" 语言中看不到它呢?
Python 代码被编译为字节码,然后在运行时解释该字节码。您的代码完全有效 Python,因此它将成功编译为字节码。但是,当解释器尝试执行该字节码时,它会在运行时抛出错误。
您可以查看 python VM 运行,使用 dis
:
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 到汇编)时,函数调用需要确切地知道函数在内存中的位置。因此,当您尝试编译一个不存在的函数时,它将无法将其转换为字节码,因为它无法查找函数位置。
PythonVM可以访问一个全局数组,可以实时修改,在这个数组中查找也是实时的。
正在做:
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 table加载函数名,在第4行创建函数后,根据名称将其存储在第6行。这样下次查找该函数时,pythonVM一定能找到。
我写了下面的函数:
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 代码在 Python VM 解释(或 JIT)之前被编译为字节码,为什么我们在编译期间看不到这个错误?为什么在我们最终看到错误之前打印了 100000 个数字?在像 C 这样的纯编译语言中,我们会在编译时看到错误,那么为什么我们在这种 "partially compiled" 语言中看不到它呢?
Python 代码被编译为字节码,然后在运行时解释该字节码。您的代码完全有效 Python,因此它将成功编译为字节码。但是,当解释器尝试执行该字节码时,它会在运行时抛出错误。
您可以查看 python VM 运行,使用 dis
:
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 到汇编)时,函数调用需要确切地知道函数在内存中的位置。因此,当您尝试编译一个不存在的函数时,它将无法将其转换为字节码,因为它无法查找函数位置。
PythonVM可以访问一个全局数组,可以实时修改,在这个数组中查找也是实时的。
正在做:
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 table加载函数名,在第4行创建函数后,根据名称将其存储在第6行。这样下次查找该函数时,pythonVM一定能找到。