为什么需要使用global关键字才能访问外部作用域中定义的变量?

3
另一个问题没有解释为什么我不能在例子(2)中不使用 'global' 改变变量 time_verf,但仍然可以在例子(4)中改变列表。
我在这个资源上发现,我不能从函数内部更改全局变量,这些示例清楚地说明了这一点。
from datetime import datetime, timedelta
time_verf = datetime.now()

我认为我明白为什么以下内容(1)是有效的:

def check_global():
    global time_verf
    clean_list = time_verf + timedelta(hours=12)  # время очистки листа
    if clean_list < datetime.now():
        list_verf.clear()
        time_verf = datetime.now()
    print('ok')

>> check_global()
<< ok

当我注释掉带有全局关键字的行(2)时,它会抛出异常:

def check_global():
    # global time_verf
    clean_list = time_verf + timedelta(hours=12)  # время очистки листа
    if clean_list < datetime.now():
        list_verf.clear()
        time_verf = datetime.now()
    print('ok')

>> check_global()
<< Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "<input>", line 3, in check_global
UnboundLocalError: local variable 'time_verf' referenced before assignment

当使用赋值语句注释掉(3)行时,再次引用该变量时可以不使用 'global' 关键字:

def check_global():
    # global time_verf
    clean_list = time_verf + timedelta(hours=12)  # время очистки листа
    if clean_list < datetime.now():
        list_verf.clear()
        # time_verf = datetime.now()
    print('ok')

>> check_global()
<< ok

但是为什么我可以更新在外部作用域中定义的列表而不使用全局变量 (4) 呢?

list = []

def check_global_list():
    list.append('check')
>> check_global_list()
>> list
<< ['check']

我在SO上看到过这个问题的答案...现在找不到那个问题了...这个想法是通过在下面的代码中有赋值行,将名称"time_verf"添加到locals中(甚至在第一行代码执行之前),我会搜索帖子以获取详细信息。 - Lohmar ASHAR
1
你的函数在注释掉的那行代码中只是调用了变量,而没有尝试修改它。我认为这涉及到可变性和不可变性的问题(列表是可变的,这就是为什么你的底部示例可以工作的原因(也适用于例如字典))。 - FObersteiner
@MrFuppes,我在示例中添加了数字,请问您是指哪个示例? - Vladimir Kolenov
1
@VladimirKolenov 我的意思是,你可以在函数内修改可变对象(例如list)而不必声明它为全局变量,这就是你在(4)中展示的内容。另一方面,对于不可变对象(例如datetime),你必须将其声明为全局变量才能在函数内进行修改(或作为参数/关键字传递),请参见你的示例(1)-(3)。 - FObersteiner
1
@VladimirKolenov,是的,我认为BoarGules对此给出了很好的解释。这与您定义变量的时间顺序(代码从上到下)有关。由于可变性可能会在脚本中引起一些混淆,因此我添加了一些关于可变性的说明。 - FObersteiner
显示剩余2条评论
3个回答

2
你找到的资源中提到:“如果我们需要给全局变量赋新值,那么可以通过将变量声明为全局变量来实现”,关键字是“assign”。你可以访问全局变量、调用它们的方法、修改它们而无需将它们声明为全局变量。只有当你需要对它们进行赋值时,才需要将它们声明为全局变量。

2
当��在第二行注释掉global time_verf语句时,它会影响到整个程序的运行。"Original Answer"的翻译是"最初的回答"。
1: def check_global():
2: # global time_verf
3: clean_list = time_verf + timedelta(hours=12)  # время очистки листа
4: if clean_list < datetime.now():
5:     list_verf.clear()
6:     time_verf = datetime.now()

第6行将一个值赋给本地变量time_verf。它是本地的,因为分配它会将其创建为本地变量。第3行出现错误,因为它引用了您在第6行中创建的本地变量。如果没有这个赋值,则time_verf默认为全局变量。

但是,变量是全局的还是不全局的并不取决于执行顺序。您似乎期望这样做,因为仅第3行将使time_verf默认为全局变量,然后它在第6行成为全局变量并保持全局。但本地变量的工作方式并非如此。第6行的存在改变了第3行的含义(和正确性)。解释器检查函数的整个范围,并为代码分配值的任何名称创建本地变量。因此,由于第6行的存在,即使仅第3行将其设置为全局变量,time_verf也是本地的。

这种行为有非常好的原因。假设第3行被包含在if测试中。然后,根据您似乎期望的行为,如果if测试为真,则变量将是全局的,但如果if测试为假,则变量将是本地的。


1

编辑:我同意Roel Schroeven的观点,核心问题在于赋值!

尽管如此,我认为BoarGules关于变量生命周期的回答很好。此外,我认为这里需要注意的一个重要点是可变性与不可变性。特别是指问题中的(4)。虽然以下代码可以正常工作

a = 2.72 # global scope, defined BEFORE the function
def modify():
    b = a + 3.14 # no assignmnet made to a (but to b in this case)
    return b # scope of b is local -> 'return' needed to make it available in the outer scope

In [106]: modify()
Out[106]: 5.86

this fails:

a = 2.72
def modify(): # assignment!
    a += 3.14 # you would need 'global a' before this to make it work
    return a

In [108]: modify()
UnboundLocalError: local variable 'a' referenced before assignment

虽然 a 可以在 modify() 中调用,但它不能被修改,因为它是类型为 float 的不可变对象。

另一方面,如果你使用可变对象如 list 来进行相同的操作,你会得到:

a = [2.72]
def modify():
    a[0] = 3.14 # change the reference to another value...
                   # no return needed

In [110]: modify()
In [110]: a
Out[110]: [3.14]

它不会失败,且即使在函数范围外改变过a,也是如此!如果在函数中调用先前未定义的变量,则会再次失败。请注意,a[0] = 3.14 不是将 a 赋值给某个值,而是更改存储在 a 中的引用到另一个值。如果您在脚本中有多个函数并传递东西,请记住这一点。
如需进一步阅读,有很好的资源可供学习,例如:this 作为入门的资源,可能还可以查看文档中的 python data model

1
在我看来,可变性与不可变性并不是问题的核心。关键在于全局变量对于赋值是必需的,这对于可变和不可变变量都是正确的。即使是列表,在执行a = a + [3.14]时也需要使用global - Roel Schroeven
@RoelSchroeven,我同意你的观点,这是问题的核心。已经进行了编辑以澄清。 - FObersteiner

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接