一个变量只在一些python嵌套函数定义的范围内,而不是全部,为什么?

2024-09-29 23:31:05 发布

您现在位置:Python中文网/ 问答频道 /正文

在下面的代码中:

def f():
    a = 'x'

    def g():
        print(a)
        if a == 'x':
            return True
        return False

    def h():
        print(a)
        def i():
            a = a + a
            return a

        a = i()

        return a

    if g():
        return h()

为什么a可以在函数g中访问,而不能在函数hi中访问?你知道吗

我不想使用nonlocal,因为我不想修改任何内部函数中的a,但是我不明白为什么a本身是不可访问的。你知道吗


Tags: 函数代码falsetruereturnifdefprint
2条回答

other answer对于解释出了什么问题非常有用。我添加了我自己的答案,试图解释问题背后的一些原因(即“为什么”而不是“什么”)。你知道吗

首先,您需要稍微了解一下Python的体系结构。我们经常将Python描述为一种“解释”语言,而不是像C这样的“编译”语言,但这并不是全部。虽然Python不直接编译为机器代码,但是当程序运行时,解释器不会在原始源代码上运行。相反,有一个中间步骤,源代码被编译成字节码。编译是在加载模块时自动进行的,因此您甚至可能不知道它(尽管您可能已经看到了编译器为缓存字节码而编写的.pyc文件)。你知道吗

无论如何,回到您的作用域问题:Python的编译器使用字节码指令来告诉解释器访问局部变量,而不是用于访问全局变量(第三条不同的指令用于从封闭函数的作用域访问变量)。由于字节码是由编译器编写的,所以要使用的字节码指令需要在函数的编译时决定,而不是在调用函数时决定。指令的选择是很棘手的,但是对于这种模棱两可的代码:

a = 1

def foo():
    if bar():
        a = 2
    print(a)

print调用的a的访问是使用读取全局变量a的字节码指令还是使用访问局部变量a的指令?编译器无法提前知道bar是否返回真值,因此没有可能的答案让函数在所有情况下都能工作。你知道吗

为了避免歧义,Python的设计人员选择变量的作用域在每个函数中都应该是常量(这样编译器就可以选择一条字节码指令并继续使用它)。也就是说,像a这样的名称可以引用局部或全局(或闭包单元),但在任何给定函数中只能引用其中一个。你知道吗

编译器默认使用局部变量(访问速度最快)作为函数代码中任何位置的赋值目标。由于内部函数与包含它们的函数同时编译,因此也可以在编译时检测到非本地查找并使用适当的指令。如果在局部作用域或封闭作用域中都找不到该名称,编译器将假定它是一个全局变量(现在还不需要定义)。globalnonlocal语句允许您显式地告诉编译器使用特定的作用域(重写它自己选择的作用域)。你知道吗

您可以使用标准库中的dis模块探索编译器在不同范围内处理变量查找的不同方式。dis模块将字节码反汇编成更可读的格式。尝试调用dis.dis函数,如下所示:

a = 1

def load_global():
    print(a) # access the global variable "a"

def load_fast():
    a = 2
    print(a) # access the local variable "a", which shadows the global variable

def closure():
    a = 2
    def load_dref():
        print(a) # access the variable "a" from the enclosing scope
    return load_dref

load_dref = closure() # both dis.dis(closure) and dis.dis(load_dref) are interesting

关于如何解释dis.dis的输出的全部细节超出了这个答案的范围(没有双关语),但是需要寻找的主要内容是LOAD_...字节码指令,这些指令将(a)作为它们的目标。在上面的不同函数中,您将看到三个不同的LOAD_...指令,对应于它们所读取的三种不同类型的作用域(每个函数都以相应的指令命名)。你知道吗

简短回答:因为分配给a(通过写入a = a + aa = i()),所以创建了局部变量。在赋值之前使用变量并不重要。你知道吗

Python通过检查赋值来检查作用域。如果你在某处写了一个赋值,比如a =a +=,等等,不管你把它写在函数的什么地方,函数都会把a看作一个局部变量。你知道吗

如果你写:

a = 2

def f():
    print(a)
    a = 3

即使在赋值给a之前访问a,它仍将a视为局部变量。Python在这里不做代码路径分析。你知道吗

它在f中看到一个局部变量。如果调用f(),则会出错,因为它会说在实际分配a之前获取它。你知道吗

如果变量不是本地定义的,Python将迭代地检查外部作用域,直到找到一个a。你知道吗

如果在作用域中赋值给,则从外部作用域访问变量的唯一方法是使用nonlocalglobal(当然也可以将其作为参数传递)。你知道吗

相关问题 更多 >

    热门问题