Python:为什么全局只需要赋值而不是读取?

Python: Why is global needed only on assignment and not on reads?

如果函数需要修改在全局范围中声明的变量,则需要使用全局声明。但是,如果函数只需要读取一个全局变量,那么它可以在不使用全局声明的情况下这样做:

1
2
3
4
5
6
X = 10
def foo():
    global X
    X = 20 # Needs global declaration
def bar():
    print( X ) # Does not need global

我的问题是关于python的设计:为什么python设计为允许在不使用全局声明的情况下读取全局变量?也就是说,为什么只有强制分配具有全局性,为什么不强制全局读取呢?(这会使它变得均匀和优雅。)

注意:我可以看到,在读取时没有歧义,但是在分配时,不清楚是要创建一个新的局部变量还是要分配给全局变量。但是,我希望有一个更好的理由或意图,这种不平衡的设计选择,由BDFL。


使用嵌套范围,变量查找很容易。它们发生在一个链中,从局部变量开始,通过封闭def,到模块全局变量,然后是内置的。规则是第一场比赛获胜。因此,查找不需要"全局"声明。

相反,对于写操作,您需要指定要写入的范围。否则,无法确定函数中的"x=10"是指"写入本地命名空间"还是"写入全局命名空间"。

执行摘要,使用write,您可以选择名称空间,但是使用查找,第一个找到的规则就足够了。希望这有帮助:—)

编辑:是的,是这样的,"因为bdfl是这么说的",但在没有类型声明的其他语言中,查找的第一个找到的规则和非本地写入只需要一个修饰符并不罕见。当您考虑它时,这两个规则会导致非常干净的代码,因为范围修饰符只在最不常见的情况下需要(非本地写入)。


查看此代码:

1
2
3
4
from module import function

def foo(x):
    return function(x)

这里的名字是一个全球性的。如果我不得不说global function才能使这段代码工作,那将是非常乏味的。

在你说你的X和我的function是不同的(因为一个是变量,另一个是导入函数)之前,记住python中的所有名称都是一样的:使用时,它们的值在作用域层次结构中被查找。如果你需要global X,那么你就需要global function。艾克。


因为显式比隐式好。

当你读一个变量的时候没有歧义。从本地搜索范围到全局搜索范围时,始终会找到第一个。

当您分配时,解释器可能明确地假设您分配给两个作用域:本地和全局。因为分配给本地是最常见的情况,而分配给全局实际上是不鼓励的,所以它是默认的。要分配给global,您必须显式地进行分配,告诉解释器在这个范围内的任何地方使用这个变量,它都应该直接转到global范围,并且您知道自己在做什么。在python 3上,您还可以使用"nonlocal"分配到最近的封闭范围。

记住,当您在python中分配一个名称时,这个新的分配与以前分配给其他对象的名称没有任何关系。想象一下,如果没有默认的local和python搜索所有范围,试图找到一个具有该名称的变量,并像读取时那样分配给它。函数的行为不仅可以根据参数,还可以根据封闭范围进行更改。生活将是悲惨的。


你自己说,读没有歧义,写有歧义。因此,您需要一些机制来解决写操作中的歧义。

一个选项(可能实际上被很多旧版本的python,iirc使用)就是说写操作总是转到本地范围。那么就不需要使用global关键字,也不需要含糊不清。但是,如果不使用globals()之类的东西来全面了解全局变量,就根本无法写入这些变量,所以这就不太好了。

静态声明变量的语言使用的另一个选项是,为每个名称是本地(在该范围中声明的名称)和全局(在模块范围中声明的名称)的范围预先与语言实现通信。但是python没有声明变量,所以这个解决方案不起作用。

另一种选择是,只有在某些外部作用域中没有名为x的名称时,才将x = 3赋值给局部变量。似乎它会直观地做正确的事情?但这会导致一些严重的角落案件。目前,x = 3将写入的位置由解析器静态地确定;要么在同一范围内没有global x,而是本地写入,要么是global x和全局写入。但是如果它要做的取决于全局模块的作用域,那么您必须等到运行时才能确定写入的位置,这意味着它可以在函数调用之间进行更改。想想看。每次在模块中创建全局时,都会更改模块中碰巧使用该名称作为局部变量名的所有函数的行为。做一些使用tmp作为临时变量的模块范围计算,并告别在模块中的所有函数中使用tmp。想到那些模糊的错误,包括在导入的模块上分配一个属性,然后从该模块调用一个函数,我就不寒而栗。讨厌。

另一种选择是在每个任务上与语言实现通信,无论它应该是本地的还是全局的。这就是Python的风格。考虑到存在几乎涵盖所有情况(写入局部变量)的合理默认,我们将局部赋值作为默认值,并使用global显式标记全局赋值。

分配中存在歧义,需要某种机制来解决它。global就是这样一种机制。这不是唯一可能的,但在Python的上下文中,似乎所有的替代机制都是可怕的。我不知道你在找什么"更好的理由"。