要了解正在发生的事情,必须了解Python在定义函数和执行函数之间的区别.
定义与执行
当Python遇到函数定义时,它将函数编译为代码对象.
然后,将代码对象用于构造功能对象.然后,在以后调用该函数时,将使用该函数对象的代码对象来执行该函数. Python不会执行该函数,它只会将该函数编译成一个对象,以后可以用于执行. Python唯一执行函数的时间就是该函数被调用的时间.
A function definition is an executable statement. Its execution binds the function name in the current local namespace to a function object (a wrapper around the executable code for the function). This function object contains a reference to the current global namespace as the global namespace to be used when the function is called.
The function definition does not execute the function body; this gets executed only when the function is called.
由于这种区别,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 "", line 1, in
func()
File "", line 2, in func
return a + b
NameError: name 'a' is not defined
如您所见,Python引发了NameError.这是因为它试图执行两个LOAD_GLOBAL指令,但是发现该名称在全局范围中未定义.
现在让我们看看如果在调用func之前定义两个变量a和b会发生什么:
>>> a = 1
>>> b = 2
>>>
>>> func()
3
上面的方法起作用的原因是因为当Python执行func的字节码时,它能够找到全局变量a和b,并使用它们来执行函数.
同样适用于示例的问题.编译main时,Python“看到”了我们试图调用一个名为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 "", line 1, in
main()
File "", line 3, in main
cubed_number = cube(number)
NameError: name 'cube' is not defined
那班呢?
Python处理类定义与处理函数定义有些不同.
当Python遇到类定义时,它将与该函数一样为该类创建一个代码对象.但是,Python还允许类具有在类定义期间执行的名称空间. Python不会等待执行类名称空间,因为定义的任何变量都应该属于该类.因此,必须定义在类名称空间内使用的任何名称,以便在类定义期间使用.
The class’s suite is then executed in a new execution frame (see Naming and binding), using a newly created local namespace and the original global namespace. (Usually, the suite contains mostly function definitions.) When the class’s suite finishes execution, its execution frame is discarded but its local namespace is saved.
但是,这不适用于方法. Python将方法中的未定义名称与函数一样对待,并允许您在定义方法时使用它们:
>>> class Class:
... def method(self):
... return var
...
>>> var = 10
>>> cls = Class()
>>> cls.method()
10