假设我在同一个python文件中一个接一个地有两个函数:
def A(n):
B(n-1)
# if I add A(1) here, it gives me an error
def B(n):
if n <= 0:
return
else:
A(n-1)
当解释器读取A
时,B
还没有定义,但是这段代码没有给我一个错误。这让我很困惑,因为我认为Python程序是逐行解释的。为什么在A
中调用B
的尝试不会立即给出错误,在调用任何东西之前?
我的理解是,当def
被解释时,Python用{"function name": function address}
向一些本地名称空间locals()
添加一个条目,但对于函数体,它只做语法检查:
def A():
# this will give an error as it isn't a valid expression
syntax error
def B():
# even though x is not defined, this does not give an error
print(x)
# same as above, NameError is only detected during runtime
A()
我写对了吗?
B(n-1)
行表示"当执行此语句时,在模块作用域中查找某些函数B
,然后带参数n-1
调用它"。由于查找是在函数执行时进行的,因此可以稍后定义B
。
(另外,您可以用不同的函数完全覆盖B
,然后A
将调用新的B
。但是这会导致一些令人困惑的代码。)
如果您担心无法捕捉到对不存在的函数的调用,您可以尝试使用静态分析工具。除此之外,请确保您正在测试您的代码。
SyntaxError
将在编译时被捕获,但大多数其他错误(NameError
, ValueError
等)将仅在运行时被捕获,然后仅当该函数被调用时。
"如果我写了一个函数,如果它在我的测试中没有被调用…"——这就是为什么你应该测试一切。
一些ide会在各种情况下发出警告,但最好的选择仍然是自己进行彻底的测试。这样,您还可以检查由诸如用户输入之类的因素引起的错误,IDE的自动检查不包括这些因素。
当解释器读取A时,B还没有定义,但是这段代码没有给我一个错误
python解释器不给出错误的原因可以从docs中找到,这在技术上称为前向引用:
自由变量的名称解析发生在运行时,而不是编译时。
具体考虑第一个示例代码:
在Python中,def A():
是一个可执行语句。通过使用块内的代码来创建一个函数对象,然后将该对象赋值给名称A
来求值。这不仅仅是一个"语法检查";它实际编译代码(在参考实现中,这意味着它为Python VM生成字节码)。该过程包括确定B
i)是一个名称,ii)将在全局中查找(因为在A
中没有赋值给 B
)。
我们可以使用标准库中的dis
模块看到结果:
>>> def A(n):
... B(n-1)
...
>>> import dis
>>> dis.dis(A)
2 0 LOAD_GLOBAL 0 (B)
2 LOAD_FAST 0 (n)
4 LOAD_CONST 1 (1)
6 BINARY_SUBTRACT
8 CALL_FUNCTION 1
10 POP_TOP
12 LOAD_CONST 0 (None)
14 RETURN_VALUE
此结果是特定于版本和实现的;我已经展示了使用Python 3.8的参考实现所得到的结果。可以推断,LOAD_GLOBAL
操作码表示在全局命名空间中查找B
的指令(如果查找不到,则在特殊的内置名称中查找)。
B
没有在全局命名空间中定义并不重要;它会在需要的时候出现。
从评论:但是为什么python在编译时不检查NameError ?
因为没有什么值得检查的名称。Python是一种动态语言;的全部要点是,您可以自由地做任何事情来确保B
在A
使用它之前被定义。例如,包括从互联网上下载另一个Python文件并在当前全局命名空间中动态执行它的极端糟糕的想法。