Python:名称解析;函数定义的顺序

26 投票
5 回答
21912 浏览
提问于 2025-04-16 11:28

我有一个非常简单的例子:

#!/usr/bin/env python

#a()  # 1: NameError: name 'a' is not defined
#b()  # 1: NameError: name 'b' is not defined
#c()  # 1: NameError: name 'c' is not defined

def a():
    c()   # note the forward use here...

#a()  #2: NameError: global name 'c' is not defined 
#b()  #2: NameError: name 'b' is not defined
#c()  #2: NameError: name 'c' is not defined

def b():
    a()

#a()   #3: NameError: global name 'c' is not defined    
#b()   #3: NameError: global name 'c' is not defined
#c()   #3: NameError: name 'c' is not defined

def c():
    pass

a()    # these all work OK...   
b()
c()

我在一个Python文件中定义了三个函数,分别叫做 a()b()c(),它们的顺序是按字母排列的。每个函数的内容都是调用其他函数。通过我的注释,你可以看到我必须把第一个函数的调用放在它们的定义下面(在文本文件中),但你不一定需要把一个函数的定义放在调用它的另一个函数的上面。

确实,通常的做法是把第一个可执行的代码放在所有函数定义的下面(在Python和许多其他语言中),现在我明白为什么了。在C和C++中,头文件会处理这个问题。在Pascal中,你必须在使用之前定义名称。

假设你在Python中有这样的代码:

def a(a_arg):          c(a_arg)
def b(b_arg):          a()
def c(a_arg,b_arg):    b(b_arg)
a(1)

它会在运行时正确地报错,提示 TypeError: c() takes exactly 2 arguments (1 given),而其他错误是在编译时就会出现。(在C中,这段代码会编译通过,但会神秘地失败...)

在Perl中,由于子程序名称通常是在运行时解析的,你可以随意排列Perl的定义和代码:

#!/usr/bin/env perl

a();
b();
c();

sub a{ c(); }
sub b{ a(); }
sub c{ return; }

在C中,使用一个没有原型的函数会导致错误或警告(这取决于具体的实现),这点不应该被忽视。

你可以这样写:

void a(void) { c(); }   /* implicitly assumed to be int c(...) unless prototyped */
void b(void) { a(); }
void c(void) { return; }

int main(void) {
    a();
    return EXIT_SUCCESS;
}

我的假设和困惑是:如果Python在运行时才解析子程序名称,为什么在源代码编译阶段会因为前向声明的子程序名称尚未定义而失败?有没有地方(除了观察其他代码)说明你不能在源文件中把代码放在子程序定义的上面?

看起来Python有动态名称解析的元素(在源文件中,a()中使用了c(),而c()的定义在下面)和静态名称解析的元素(如果把a()的调用放在它的定义上面,Python会失败)。

有没有Python版本的这份文档,讲述Perl可执行文件的生命周期以及如何在源文件解释和运行时解析名称?

有没有明确的描述,说明Python脚本中定义的顺序,指出函数可以有其他子程序名称的前向定义,但主代码不能?

编辑和结论

经过一些热烈的评论和我自己的研究,我得出的结论是,我的问题其实更多是关于名称是如何解析的,以及Python中命名空间、作用域和模块是如何定义的。

来自carot-top的说法:

"一个可调用的对象必须在当前命名空间中被调用之前定义。" 还有这个链接关于作用域和名称的内容。

来自S.Lott的说法:

"当在代码块中使用一个名称时,它会在最近的封闭作用域中被解析。" 还有这个链接讲述Python脚本的执行生命周期。

来自Python文档的内容:

"一个作用域定义了名称在一个块内的可见性。" 来自Python执行模型

"一个模块可以包含可执行语句以及函数定义。" 在关于模块的更多内容中。

"实际上,函数定义也是被‘执行’的‘语句’;模块级函数的执行会将函数名称输入到模块的全局符号表中。" 在相关的脚注中。

还有我自己的领悟(哎呀!)是:

  1. 每个Python源文件都被Python视为一个“模块”: “模块是一个包含Python定义和语句的文件。”

  2. 与我更熟悉的Perl不同,Python在读取模块时就会执行它们。因此,如果在同一个模块中引用一个尚未定义的函数,就会导致失败。

5 个回答

3

这和C/C++等语言是一样的。你不能在某个东西还不存在的时候就去使用它。在C/C++中,你必须先声明一个东西,才能在后面使用它。要记住,Python文件是从上到下逐行处理的,所以如果你试图调用一个不存在的函数或者引用一个不存在的变量,就会出错。

5

根据各种评论和从Python的角度理解一些Perl概念的困难,我来试着解释一下。请先把你在Perl学到的一些东西“重置”一下,因为它们在Python中并不适用。(比如“与” vs “与”...)

在Python中没有“前向声明”。没有。技术上讲,所有的函数都是匿名对象;它们只是绑定在你用来定义它们的名字上。你可以随意重新绑定它们。

你可以通过 locals() 函数找到这些函数的字典,方法如下:

>>> def a(): b()
... 
>>> locals()['a']
<function a at 0x100480e60>
>>> locals()['b']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'b'
>>> a()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in a
NameError: global name 'b' is not defined

如果Python要求在写 a() 之前定义 b(),那么在Python解释器中就会出现问题。你需要严格按照顺序写所有函数。

由于所有内置函数的名字只是绑定的名字,你可以轻松地覆盖内置函数:

>>> abs(-1)
1
>>> def abs(num): print "HA Fooled you!!!"
... 
>>> abs(-1)
HA Fooled you!!!
>>> abs=__builtins__.abs
>>> abs(-1)
1

在Perl中覆盖内置函数要困难得多(但也是可能的)。这里的缺点是,如果你不小心打错了 def [builtin]:,可能会在没有警告的情况下覆盖掉内置函数。

我能推荐给你关于Python中名字和作用域的最佳描述其实是关于 类的教程 -- 第9.2节

实际上并没有明确的理由说明为什么 def 必须在可执行代码之前,因为这并不是一个绝对的说法。考虑一下:

#!/usr/bin/env python

def fake_a(): print " a fake"
a=fake_a
a()  
def a():  print "this is a()"
a()

甚至可以是:

def a(): print " first a()"
a()  
def a():  print "second a()"
a()

真正的情况是一个可调用的对象必须在当前命名空间中被定义后才能被调用。因此,通常是在源文件或模块中被调用的可执行代码之前。每个函数都有自己的命名空间;对尚未定义的其他函数的调用只有在该函数在本地和执行的命名空间中被调用时才会失败——当可调用对象从函数的命名空间中被调用时。这就是为什么在你的例子中看起来像是“前向声明”。在函数外部调用一个“前向”可调用对象会失败,因为函数 def 还没有被执行,所以它不在当前命名空间中。

27

定义的顺序很简单,就是“你必须在调用之前先定义它”。就这么简单。

编辑(为了包含评论中的答案,进一步解释):

像下面这样的代码:

def call_a():
    a()

def a():
    pass

call_a()

之所以能在 call_a() 中调用 a(),而 a 还没有被定义为一个函数,是因为 Python 只在需要的时候才会查找符号的值。当 call_a 被执行时,a() 的调用实际上是作为字节码指令被存储的,指示“查找 a 是什么并在需要的时候调用它”,而这个“需要的时候”就是在你实际调用 call_a() 的时候。

这是 call_a 的反汇编字节码的样子(通过 dis.dis):

Disassembly of call_a:
  2           0 LOAD_GLOBAL              0 (a)
              3 CALL_FUNCTION            0
              6 POP_TOP
              7 LOAD_CONST               0 (None)
             10 RETURN_VALUE

所以基本上,当你调用 call_a 时,它会把存储的 a 加载到栈上,作为一个函数调用它,然后在返回之前把返回值弹出,最后返回 None。这就是对于任何没有明确返回值的情况,隐式发生的事情(call_a() 是 None 返回 True)。

撰写回答