在下面的代码中,我定义了两个函数。 main
和cube
。我希望main
成为我的计划的开始,所以我在cube
内打了main
:
>>> def main():
number = int(input('Enter a number: '))
cubed_number = cube(number)
print("The number cubed is: ", cubed_number)
>>> def cube(number):
return number * number * number
>>>
但是我在cube
之后定义了main
,所以我认为我的代码会引发NameError
。但是,Python没有引发异常,而是完美地执行了我的代码:
>>> main()
Enter a number: 5
The number cubed is: 125
>>>
发生什么事了?为什么Python能够在不知道cube
如何定义的情况下运行我的代码?为什么NameError
被募集?
更奇怪的是,当我尝试用类做同样的事情时,Python确实引发了NameError
:
>>> class Main:
cubed_number()
Traceback (most recent call last):
File "<pyshell#27>", line 1, in <module>
class Main:
File "<pyshell#27>", line 2, in Main
cubed_number()
NameError: name 'cubed_number' is not defined
>>>
这里发生了什么?
注意:这不是Why can I call a function before defining it, with only a warning?的重复,因为那里的答案并不能解释为什么或如何在Python中使用此行为。我创建了这个Q&amp; A,因为目前这类问题的答案分散在各种问题中。我也觉得展示幕后发生的事情是有益的。随意编辑和改进Q&amp; A。
答案 0 :(得分:4)
要理解发生了什么,必须理解Python在定义函数和执行函数之间的区别。
当Python遇到函数定义时, 将 函数编译成代码对象。
代码对象是Python用于保存与特定可执行代码块关联的字节码的内部结构。它还包含Python执行字节码所需的其他信息,例如常量和局部变量名。 documentation gives a much more more extensive overview of what code objects are。
然后使用代码对象构造函数对象。然后,函数对象的代码对象用于在稍后调用该函数时执行该函数。 Python不会 执行 该函数,它只会将函数编译成一个可以在以后执行的对象。 Python执行函数的唯一时间是函数被调用 。
以下是the documentation which mentions this的相关部分:
函数定义是可执行语句。它的执行将当前本地命名空间中的函数名称绑定到一个函数对象(该函数的可执行代码的包装器)。此函数对象包含对当前全局命名空间的引用,作为调用函数时使用的全局命名空间。
函数定义不执行函数体;只有在调用函数时才会执行此操作。
由于这种区别,Python无法验证名称是否实际定义为,直到调用该函数。因此,您可以在函数体中使用当前不存在的名称。只要在调用函数时定义了名称,Python就不会引发错误。
这是一个例子。我们定义了一个函数func
,它将两个变量加在一起; a
和b
:
>>> def func():
... return a + b
正如您所看到的,Python没有引发任何错误。这是因为它只编译了func
。它没有尝试执行该功能,因此没有看到a
和b
未定义。
我们可以反汇编func
的代码对象,并使用dis
模块查看字节码的外观。这将告诉我们更多关于Python正在做什么的事情:
>>> from dis import dis
>>> dis(func)
2 0 LOAD_GLOBAL 0 (a)
2 LOAD_GLOBAL 1 (b)
4 BINARY_ADD
6 RETURN_VALUE
Python在字节码中编码了两条LOAD_GLOBAL
指令。指令的参数分别是变量名a
和b
。
这表明Python确实看到我们在编译函数时尝试引用两个变量,并创建了字节码指令。 但在调用函数 之前,它不会尝试实际执行指令。
让我们看看当我们尝试通过调用它来执行func
的字节码时会发生什么:
>>> func()
Traceback (most recent call last):
File "<pyshell#15>", line 1, in <module>
func()
File "<pyshell#14>", line 2, in func
return a + b
NameError: name 'a' is not defined
正如您所看到的,Python引发了NameError
。这是因为它试图执行两条LOAD_GLOBAL
指令,但发现全局范围内未定义的名称。
现在让我们看看如果我们在调用a
之前定义了两个变量b
和func
会发生什么:
>>> a = 1
>>> b = 2
>>>
>>> func()
3
上述原因之所以如此,是因为当Python执行func
的字节码时,它能够找到全局变量a
和b
,以及用这些来执行函数。
这个例子同样适用于问题。编译main
时,Python&#34;看到&#34;我们试图调用名为cube
的变量并生成一条指令来获取cube
的值。但是在执行指令之前,它没有尝试查找名为cube
的可调用对象。在执行main
字节代码时(例如调用main
),定义了一个名为cube
的函数,因此Python没有引发错误。
如果我们在定义多维数据集之前尝试调用main ,那么我们在上面的示例中出于同样的原因会出现名称错误:
>>> def main():
... number = int(input('Enter a number: '))
... cubed_number = cube(number)
... print("The number cubed is: ", cubed_number)
...
>>> main()
Enter a number: 23
Traceback (most recent call last):
File "<pyshell#23>", line 1, in <module>
main()
File "<pyshell#22>", line 3, in main
cubed_number = cube(number)
NameError: name 'cube' is not defined
Python处理类定义与函数定义略有不同。
当Python遇到类定义时,它会像使用函数一样为类创建一个代码对象。但是,Python还允许类具有在类定义期间执行的名称空间。 Python不等待执行classes命名空间,因为定义的任何变量都应属于该类。因此,必须在类定义时间内定义类名称空间内使用的任何名称。
documentation for class definitions touches on this:
然后使用新创建的本地命名空间和原始全局命名空间,在新的执行框架中执行类的套件(请参阅命名和绑定)。 (通常,套件主要包含函数定义。)当类的套件完成执行时,它的执行帧被丢弃,但它的本地名称空间被保存。
但是,这不适用于方法。 Python像处理函数一样处理方法中的未定义名称,并允许您在定义方法时使用它们:
>>> class Class:
... def method(self):
... return var
...
>>> var = 10
>>> cls = Class()
>>> cls.method()
10