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

daw*_*awg 19 python namespaces function

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

#!/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()
Run Code Online (Sandbox Code Playgroud)

我有3个名为的函数a(),b()并按c()字母顺序在Python源文件中定义.每个函数定义的主体是对其他函数之一的调用.您可以通过我的注释看到我必须首先调用这些函数中的第一个函数(在文本文件中),但是您不一定需要在调用它的另一个函数之上定义函数.

当然,通常的做法是将第一个可执行代码放在所有函数定义(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)
Run Code Online (Sandbox Code Playgroud)

它将TypeError: c() takes exactly 2 arguments (1 given)在运行时正常失败,其他错误是编译时.(在C中,这会编译然后神秘地失败...)

在Perl中,由于子例程名称在运行时是USUALLY解析的,因此您可以按任何顺序使用Perl定义和代码:

#!/usr/bin/env perl

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

sub a{ c(); }
sub b{ a(); }
sub c{ return; }
Run Code Online (Sandbox Code Playgroud)

在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;
}
Run Code Online (Sandbox Code Playgroud)

我的假设和困惑是:如果Python直到运行时才解析子程序名称,为什么源编译阶段失败并且尚未定义子程序名称的前向声明?是否在某个地方(除了观察其他代码之外)记录了在子程序定义之上的源文件中没有代码?

似乎Python有元素动态名称解析(利用的c()a()其源文件中的下面的定义之前)和静态名称解析(Python中的失败运行调用的元素a(),如果放置在其定义的上方在源文件中.)

是否有一个Python版本的THIS DOCUMENT涵盖了Perl可执行文件的生命周期以及如何在源文件解释和运行时之间解析名称?

在Python脚本的定义顺序上是否存在明确的描述,该脚本声明函数可以具有其他子例程名称的前向定义,但主代码不能?

编辑和结论

经过一些精彩的评论和我的一些研究之后,我得出结论,我的问题更多的是如何解析名称,以及如何在Python中定义名称空间,范围和模块.

carot-top:

"必须先定义一个callable,然后才能在当前命名空间中调用它." 以及关于范围和名称的这个链接

来自S.Lott:

"当在代码块中使用名称时,使用最近的封闭范围来解析它." 与此链接到Python脚本的执行寿命.

从Python文档:

"范围定义了块内名称的可见性." 来自Python执行模型

"模块可以包含可执行语句和函数定义." 在更多的模块

"事实上,函数定义也是'执行'的'语句';模块级函数的执行在模块的全局符号表中输入函数名." 在其脚注中.

我自己的认识(呃!):

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

  2. 与Perl(我有更多经验)不同,Python在读取时执行模块.因此,直接可执行语句的失败引用了尚未在同一模块中定义的函数.

Dan*_*olo 25

定义的顺序只是"在调用它之前必须定义所有内容".这就是它.

编辑 (包括评论中的答案,阐明):

之所以如此

def call_a():
    a()

def a():
    pass

call_a()
Run Code Online (Sandbox Code Playgroud)

工作原理,当你有a()call_a()之前a甚至定义为一个功能是因为Python实际上只查找值的上的符号按需的基础.在call_a评估时,a()调用基本上存储为字节码指令,以便在时间到来时 "查找是什么a并调用它" ,直到您进入底部的实际调用.call_a()

这是call_a看起来像(via 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
Run Code Online (Sandbox Code Playgroud)

所以基本上,当你点击时call_a,它会将存储的任何内容加载a到堆栈中,将其作为函数调用,然后在返回之前弹出返回值None,这对于任何未显式返回的内容都会隐式发生(call_a() is None返回True)

  • 是的,在定义之前,您实际上并未调用任何函数.当它评估函数定义时,它只是说"好吧,当有人打电话给我时,我会在符号表中查找`c`",但那时候并不重要. (2认同)

the*_*olf 5

遵循各种评论并努力理解Python历史中的一些Perl概念,让我对此进行分析.请重新调整你在Perl中学到的一些东西.它们不适用于Python.(和vs 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
Run Code Online (Sandbox Code Playgroud)

如果b()在编写之前需要定义Python a(),那么这将是Python解释器中的一个问题.您需要严格按顺序编写所有函数.

由于所有内置函数名称都只是有界名称,因此您可以轻松覆盖内置函数:

>>> abs(-1)
1
>>> def abs(num): print "HA Fooled you!!!"
... 
>>> abs(-1)
HA Fooled you!!!
>>> abs=__builtins__.abs
>>> abs(-1)
1
Run Code Online (Sandbox Code Playgroud)

在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()
Run Code Online (Sandbox Code Playgroud)

甚至:

def a(): print " first a()"
a()  
def a():  print "second a()"
a()
Run Code Online (Sandbox Code Playgroud)

真实的是,必须在当前命名空间中调用callable之前定义它.因此,通常,高于源文本文件或模块中的可执行代码调用的点.每个函数都有自己的命名空间; 当在函数的命名空间中调用callable时,在该本地和正在执行的命名空间中调用该函数时,对尚未定义的其他函数的调用仅失败.这就是为什么你可以在你的例子中看起来像一个"前向声明".函数外部的"转发"可调用模块调用失败,因为该函数def尚未执行,因此它不在当前命名空间中.

  • 我想你是对的.我在想,引用尚未定义的函数的能力意味着某种前向引用能力.它更像是一个"滚动命名空间" (2认同)