发生了两件事:
- 当您从
guts 导入* 时,test_x 被导入到module 的命名空间,并具有1 的值。
- 当您运行
inc_x 时,您声明 test_x 全局,即在其模块 (guts) 内是全局的。
现在由于int 是不可变的(并且不/不能支持就地递增),它最终/有效地最终将2(递增值)分配给test_x,它对guts 是全局的。
您可以尝试这两个可以证明的事情,替换:
print(f"after in test : {module.test_x}")
与:
import module.guts
print(f"after in test : {module.guts.test_x}")
经过同样的操作,module.test_x 为 1,但module.guts.test_x 显示了你所期望的递增值。
您也可以将guts 更改为如下所示:
test_x = [1]
def inc_x():
global test_x
test_x[0] += 1
def print_x(prefix):
print(f"{prefix} in module: {test_x[0]}")
并测试匹配:
x = module.test_x[0]
print(f"before in test : {module.test_x[0]}")
module.print_x("before")
print("incrementing...")
module.inc_x()
print(f"after in test : {module.test_x[0]}")
module.print_x("after ")
assert x==module.test_x[0]-1
print("SUCCESS!!\n")
现在,当您导入 guts 时,module.test_x 是一个列表(与 module.guts.test_x 完全相同)。当您操作该列表中的项目时,您仍然在访问同一个实例,无论它是通过guts.test_x(也作为其在inc_x 中的全局)还是通过module.test_x。 -> 您没有为guts.test_x 分配新值;您更改了 module.test_x 所指的对象。
也就是说,我通常对global 有点怀疑。是的,它们是混淆阅读代码的任何人的好方法。
关于您的进一步询问:每个对象都有其身份(并且驻留在内存中的某个位置)。变量(名称)是对这些对象的引用。分配给变量会建立对该对象的引用:
>>> class C: pass
...
>>> a = C() # create new instance and assign it to a
>>> b = a # assign that instance to b
>>> c = C() # create new instance and assign it to c
>>> id(a) == id(b) # or: a is b
True
>>> id(a) == id(c) # or: a is c
False
a 和 b 是指同一个对象的不同名称。
>>> a.a = 1
>>> print(b.a)
1
但是:
>>> a = 1
>>> b = a
>>> id(a) == id(b) # or: a is b
True
>>> b = 2
>>> id(a) == id(b) # or: a is b
False
>>> a
1
这里发生了什么?我有一个对象(字面值int 的值1),我已经分配了一个名称(变量)a,然后我还让b 引用了这个相同的对象。但是,对于b = 2,我没有更改对象的值,而是重新分配b 所指的内容(字面值int 与值2)。
在第一个示例中,更改class C 实例的属性a。我正在修改对象......两者都指向同一个对象(实例)。在这第二个例子中。我已更改(重新分配)引用,a 和 b 不再指向同一个对象。
在您的函数中,您使用了 += 运算符,它将尝试就地添加,但 int 不(也不能)支持此操作(因为它是不可变类型:它无法更改到位)。 IE。创建一个新对象并重新分配名称以引用结果(在这种情况下a += 1 与a = a + 1 具有相同的效果)。
现在,即使您的变量(名称)是global,它仍然只是其模块中的全局变量。你可以使用globals检查全局变量,两个小文件m1.py
script_a = 1
# prune keys starting with "__" from printed dict
print("in m1:", {k: v for (k, v) in globals().items() if not k.startswith("__")})
和script.py:
import m1
m1_a = 1
print("in script:", {k: v for (k, v) in globals().items() if not k.startswith("__")})
会给你:
$ python3 script.py
in m1: {'script_a': 1}
in script: {'m1': <module 'm1' from '/tmp/m1.py'>, 'm1_a': 1}
这意味着当由于from ... * 导入而创建名称时,会创建一个新名称。虽然最初两个名称都指向同一个对象,但当您调用 guts 的 inc_x 时,它会被重新分配并指向新的东西(添加的结果),而 module 中的另一个名称仍然指向原始对象(1 )。
现在正如提示的那样。我通常不鼓励使用global 变量,因为它们可能导致不太明显的行为,并且可能使未来的脚本阅读和维护更加困难。也就是说,虽然可以通过使用类型(例如list)并就地修改对象来获得您想要的行为。跨越模块边界将其带到另一个令人困惑的层次。确实很难立即看到代码的一部分更改的效果以及它可能在其他地方产生的(可能无法预料的)效果(从同一模块导入的内容也被导入并在更大的脚本中使用)。