为什么我可以在Python中定义函数之前使用变量?

时间:2017-07-11 18:39:17

标签: python function class

在下面的代码中,我定义了两个函数。 maincube。我希望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。

1 个答案:

答案 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,它将两个变量加在一起; ab

>>> def func():
...     return a + b

正如您所看到的,Python没有引发任何错误。这是因为它只编译了func。它没有尝试执行该功能,因此没有看到ab未定义。

我们可以反汇编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指令。指令的参数分别是变量名ab

这表明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之前定义了两个变量bfunc会发生什么:

>>> a = 1
>>> b = 2
>>> 
>>> func()
3

上述原因之所以如此,是因为当Python执行func的字节码时,它能够找到全局变量ab,以及用这些来执行函数。

这个例子同样适用于问题。编译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