导入封闭命名空间时,全局变量的更改会丢失

Change of a global variable gets lost when importing the enclosing namespace

提问人:Tortar 提问时间:8/24/2022 最后编辑:Tortar 更新时间:6/6/2023 访问量:267

问:

我在玩范围和命名空间时,我发现了一个奇怪的行为,我不知道如何解释。假设我们有一个名为 new_script.py 的文件,里面有

a = 0

def func():
    import new_script #import itself
    new_script.a += 1
    print(new_script.a)

func()
print(a)

执行时打印

1 
1 
2 
0

没想到最后打印了这个数字.据我了解,它打印前两个执行 self-import 语句递增全局,然后它打印,因为它从函数内部再次递增全局,但为什么最后一次打印不是?01a2a02

python 导入 范围 命名空间 局变量

评论

0赞 quamrana 8/24/2022
最后一个来自上次运行的时间。这是因为 in 永远不会递增。0print(a)a__main__
0赞 dragonfire_007 8/24/2022
这种脚本的用例是什么?
2赞 tituszban 8/24/2022
@dragonfire_007这不是脚本本身的使用。它理解语言是如何工作的。

答:

4赞 Yevhen Kuzmovych 8/24/2022 #1

TL的;DR:你有两个不同的变量和 .你只改变.__main__.anew_script.anew_script.a

要通过以下方式进行跟踪:

a = 0

在模块中定义一个变量。a__main__

def func(): ...

在模块中定义一个函数。func__main__

func()

在模块中调用此函数。在功能中:__main__

import new_script

imports 模块,其中:new_script

a = 0
def func(): ...

在模块中定义 和 ( 和afuncnew_scriptnew_script.anew_script.func)

func()

来自模块的调用。在功能中:funcnew_script

import new_script

好吧,我们已经导入了它,所以我们不会再次导入它

new_script.a += 1
print(new_script.a)

递增并打印它(我们的第一个)。然后new_script.a1

print(a)

打印(又名)从模块(我们的第二个)anew_script.anew_script1)

new_script完成了。 返回执行:__main__

new_script.a += 1
print(new_script.a)

第二次递增并打印它(我们的 )。new_script.a2

最后:

print(a)

从未更改过的印刷(又名 )(所以a__main__.a0)

2赞 tituszban 8/24/2022 #2

好吧,这导致了一个非常有趣的 rabit 洞。所以谢谢你。

以下是关键点:

  • 导入不会递归。如果导入一次,它将执行模块级代码,但如果再次导入,则不会再次执行。因此,您只能看到 4 个值。
  • 进口是单例。如果您尝试以下代码:
# singleton_test.py
import singleton_test

def func():
    import singleton_test #import itself
    print(singleton_test.singleton_test == singleton_test)

func()

它将打印:

True
True
  • 导入的模块单例版本与模块的原始运行版本不同

考虑到这一点,我们可以通过用更多的注释来丰富你的代码,特别是使用包含当前模块名称的注释,如果当前模块是最初运行的模块,这将是:__name____main__

a = 0

print("start", __name__)

def func():
    print("Do import", __name__)
    import new_script #import itself
    new_script.a += 1
    print(new_script.a, "func", __name__)

func()
print(a, "outr", __name__)

这将打印

start __main__
Do import __main__
start new_script
Do import new_script
1 func new_script
1 outr new_script
2 func __main__
0 outr __main__

这很好地表明,假设导入的模块是单例(但不是运行的模块),您

  • 在模块内的函数内递增值后,首先在函数中打印 1
  • 然后在导入模块的末尾打印 1
  • 然后,在原始运行代码上将单例上的值递增后打印 2
  • 最后,您为最初运行但尚未触及的未更改的外部模块打印 0。

评论

0赞 Tortar 8/24/2022
谢谢你的解释!我认为的关键点是导入模块会创建一个与原始模块不同的命名空间!我只有一个问题,你能更好地说明你所说的单例是什么意思吗?
0赞 Tortar 8/24/2022
也许您的意思是将其导入两个不同的范围不会使它们不同?
0赞 tituszban 6/6/2023
@Tortar 通过单例,我指的是常见的单例模式,即代码库中只有一个实体实例