【问题标题】:Python global keywordPython 全局关键字
【发布时间】:2017-07-28 10:50:55
【问题描述】:

我对下面代码 sn-p 中的全局关键字行为感到困惑,我期望在所有 3 个打印中都有 30、30、30。

def outer_function():
    #global a  ###commented intentionally
    a = 20
    def inner_function():
        global a
        a = 30
        print('a =',a)

    inner_function()
    print('a =',a)

a = 10
outer_function()
print('a =',a)
#Output:
#30
#20  #Expecting 30 here
#30

所有的混乱都来自外部函数定义之后的“全局 a”。正如我此时的理解是“对变量的所有引用和赋值都全局反映在该变量上的全局关键字声明中”。如果我取消注释第一个全局语句,我会得到预期的输出 30,30,30。

为什么内部函数内部的全局声明和值更改不会反映在第二次打印上,即到外部函数(或外部范围),而反映在全局命名空间中。

【问题讨论】:

  • 我不太确定您对什么感到困惑。如果您不在那里使用 global,那么在该范围内对 a 的引用不是全局的。为什么这令人惊讶?
  • 因为第二次打印不在inner_function中。

标签: python


【解决方案1】:

熟悉的常见首字母缩写词是LEGB

  • 本地本地
  • E关闭
  • G全局
  • B内置

这是 Python 搜索命名空间以查找变量分配的顺序。

本地

本地命名空间是当前代码块中发生的一切。函数定义包含局部变量,这是 Python 查找变量引用时首先找到的东西。在这里,Python 将首先在foo 的本地范围内查找,找到分配了2x 并打印出来。尽管x 也在全局命名空间中定义,但所有这些都会发生。

x = 1
def foo():
    x = 2
    print(x)

foo()
# prints:
2

当 Python 编译一个函数时,它决定定义代码块中的每个变量是局部变量还是全局变量。为什么这很重要?让我们看一下foo 的相同定义,但翻转其中的两行。结果可能令人惊讶

x = 1
def foo():
    print(x)
    x = 2

foo()
# raises:
UnboundLocalError: local variable 'x' referenced before assignment

出现此错误的原因是,由于分配了x = 2,Python 将x 编译为foo 内的local 变量。

您需要记住的是,局部变量只能访问其自身范围内的内容。

封闭

定义多层函数时,未编译为本地的变量将在下一个最高命名空间中搜索它们的值。这是一个简单的例子。

x = 0

def outer_0():
    x = 1
    def outer_1():
        def inner():
            print(x)

        inner()

    outer_1()

outer_0()
# print:
1

inner() 被编译时,Python 将x 设置为全局变量,这意味着它将尝试访问本地范围之外的x 的其他赋值。 Python 在封闭的命名空间中向上搜索 x 值的顺序。 x 不包含在outer_1 的命名空间中,因此它检查outer_0,找到一个值并将该分配用于inner 中的x

x --> inner --> outer_1 --> outer_0 [ --> global, not reached in this example]

您可以使用关键字nonlocalglobal 强制变量不是本地变量(注意:nonlocal 仅在 Python 3 中可用)。这些是编译器关于变量范围的指令。

nonlocal

使用nonlocal 关键字告诉python 将变量分配给在命名空间向上移动时找到的第一个实例。对变量所做的任何更改也将在变量的原始命名空间中进行。在下面的例子中,当2被分配x时,它也是在outer_0的范围内设置x的值。

x = 0

def outer_0():
    x = 1
    def outer_1():
        def inner():
            nonlocal x
            print('inner  :', x)
            x = 2
        inner()
    outer_1()
    print('outer_0:', x)

outer_0()
# prints:
inner  : 1
outer_0: 2

全球

全局命名空间是您程序运行的最高级别命名空间。它也是所有函数定义的最高封闭命名空间。一般来说,将值传入和传出全局命名空间中的变量并不是一个好习惯,因为可能会发生意外的副作用。

global

使用global 关键字类似于非本地关键字,但不是向上通过命名空间层,而是在全局命名空间中搜索变量参考。使用上面的相同示例,但在这种情况下,声明 global x 告诉 Python 在全局命名空间中使用 x 的分配。这里全局命名空间有x = 0:

x = 0

def outer_0():
    x = 1
    def outer_1():
        def inner():
            global x
            print('inner  :', x)
        inner()
    outer_1()

outer_0()
# prints:
0

同样,如果一个变量还没有在全局命名空间中定义,它会引发错误。

def foo():
    z = 1
    def bar():
        global z
        print(z)
    bar()

foo()
# raises:
NameError: name 'z' is not defined

内置

最后,Python 会检查内置关键字。诸如 listint 之类的原生 Python 函数是 Python 检查 AFTER 检查变量的最终参考。您可以重载原生 Python 函数(但请不要这样做,这是个坏主意)。

这是一个你不应该做的事情的例子。在dumb 中,我们通过在dumb 的范围内将其分配给0 来重载本机Python list 函数。在even_dumber 中,当我们尝试使用list 将字符串拆分为字母列表时,Python 将在dumb 的封闭命名空间中找到对list 的引用并尝试使用它,从而引发错误。

def dumb():
    list = 0
    def even_dumber():
        x = list('abc')
        print(x)
    even_dumber()

dumb()
# raises:
TypeError: 'int' object is not callable

您可以通过引用list 的全局定义来恢复原始行为:

def dumb():
    list = [1]
    def even_dumber():
        global list
        x = list('abc')
        print(x)
    even_dumber()

dumb()
# returns:
['a', 'b', 'c']

但同样,不要这样做,这是不好的编码习惯。

我希望这有助于揭示命名空间在 Python 中的一些工作方式。如果您想了解更多信息,Luciano Ramalho 撰写的 Fluent Python 第 7 章对 Python 中的命名空间和闭包进行了精彩的深入演练。

【讨论】:

  • @James-非常感谢。一个非常有帮助的答案。
【解决方案2】:

来自documentation

全局声明是一个声明,它适用于整个 当前代码块。这意味着列出的标识符将是 解释为全局变量。

请注意,它仅适用于当前代码块。所以inner_function 中的global 只适用于inner_function。在它之外,标识符不是全局的。

请注意“标识符”与“变量”的不同之处。所以它告诉解释器的是“当我在这个代码块中使用标识符a 时,不要应用正常的范围解析,我实际上是指模块级变量,”

【讨论】:

    【解决方案3】:

    只需在 external_function 中取消注释您的全局命令,否则您将声明一个值为 20 的局部变量,更改一个全局变量然后打印相同的局部变量。

    【讨论】:

      【解决方案4】:

      使用全局变量不是一个好主意。如果您只想重置变量的值,只需使用以下行:

      def outer_function():
      a = 20
      def inner_function():
               a = 30
               print('a =',a)
               return a
      
          a = inner_function()
          print('a =',a)
          return a
      
      a = 10
      a = outer_function()
      print('a =',a)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-06-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-06-22
        相关资源
        最近更新 更多