python: 持有对 lambda 函数的引用的导入全局变量的状态
python: state of imported global variables holding a reference to lambda function
背景故事:
我正在尝试实现一种处理 -v
参数的方法,以增加应用程序的冗长程度。为此,我想使用一个全局变量,它最初指向一个空的 lambda 函数。如果给定 -v
,则变量会更改并分配另一个 lambda 函数,该函数会打印它的输入。
MWE:
我注意到在通过 from x import *
...
导入后从另一个模块调用 lambda 函数时,这没有按预期工作
mwe.py:
from mod import *
import mod
def f():
vprint("test in f")
vprint("test before")
print("before: %d" % foo)
set_verbosity(1)
vprint("test after")
print("after: %d" % foo)
f()
mod.vprint("explicit: %d" % mod.foo)
modf()
mod.py:
vprint = lambda *a, **k: None
foo = 42
def set_verbosity(verbose):
global vprint, foo
if verbose > 0:
vprint = lambda *args, **kwargs: print(*args, **kwargs)
foo = 0
def modf():
vprint("modf: %d" % foo)
输出为
before: 42
after: 42
explicit: 0
modf: 0
其中 "explicit" 和 "modf" 输出是由于 mwe 末尾的 mod.vprint
和 modf
调用。 vprint
的所有其他调用(通过 vprint
的导入版本)显然没有使用更新的定义。同样,foo
的值似乎只导入了一次。
问题:
在我看来,from x import *
类型的导入复制了导入模块的全局状态。我对解决方法本身不太感兴趣,但对这种行为的实际原因很感兴趣。这是在文档中的什么地方定义的?基本原理是什么?
解决方法:
作为旁注,无论如何实现这一点的一种方法是通过函数包装全局 lambda 变量并仅导出它们:
_vprint = lambda *a, **k: None
def vprint(*args, **kwargs):
_vprint(*args, **kwargs)
def set_verbosity(verbose):
global _vprint
if verbose > 0:
_vprint = lambda *args, **kwargs: print(*args, **kwargs)
这使得它与 import-from 方式一起工作,允许其他模块简单地调用 vprint
而不是通过模块名称显式引用。
前方黑夜中随机一刺:
If you look at the docs for __import__
,有一点:
On the other hand, the statement from spam.ham import eggs, sausage as saus
results in
_temp = __import__('spam.ham', globals(), locals(), ['eggs', 'sausage'], 0)
eggs = _temp.eggs
saus = _temp.sausage
我认为这是关键。如果我们推断 from mod import *
结果类似于:
_temp = __import__('mod', globals(), locals(), [], 0)
printv = _temp.printv
foo = _temp.foo
这表明问题所在。 printv
是对旧版本printv
的引用; mod.printv
在导入时指向的内容。重新分配 mod
中的 printv
指向的内容不会影响 mwe
中的任何内容,因为 mwe
对 printv
的引用仍在查看之前的 lambda .
这和这个不变的方式类似b
:
a = 1
b = a
a = 2
b
仍然指向 1
,因为重新分配 a
不会影响 b
正在查看的内容。
另一方面,mod.printv
确实有效,因为我们现在在mod
中使用对全局的直接引用而不是指向的引用mod
.
中的 printv
这是一个随机刺探,因为我想我根据前一段时间的一些随机阅读知道了答案。如果我不正确,请告诉我,我会删除它以避免混淆。
TL;DR:当您执行 from module import *
时,您是在 复制名称及其相关引用 ;更改与原始名称关联的引用不会更改与副本关联的引用。
这处理了 names 和 references 之间的根本区别。这种行为的根本原因与 python 处理此类事情的方式有关。
在python中只有一件事是真正不变的:记忆。您不能直接更改单个字节。但是,python 中的几乎所有内容都处理 对 单个字节的引用,您可以更改这些引用。当您执行 my_list[2] = 5
时,您并没有更改任何内存 - 相反,您正在分配一个新的内存块来保存值 5
,并将 my_list
的第二个索引指向它。 my_list[2]
曾经指向的原始数据仍然存在,但由于不再引用它,垃圾收集器最终会处理它并释放它正在使用的内存。
同样的原则也适用于名字。 python 中的任何给定名称空间都与 dict
相当 - 每个名称都有相应的引用。这就是问题所在。
考虑以下两个语句之间的区别:
from module import *
import module
在这两种情况下,module
都被加载到内存中。
在后一种情况下,只有一件事被添加到本地命名空间——名称'module'
,它引用包含刚刚加载的模块的整个内存块。或者,嗯,它引用了该模块自己的命名空间的内存块,它本身引用了模块中的所有名称,依此类推。
然而,在前一种情况下,module
命名空间中的每个名称都被复制到本地命名空间中。同一个内存块仍然存在,但是现在 many[=] 不再是 one 对 all 的引用111=] 引用了它的 小部分 。
现在,假设我们连续执行这两个语句:
from module import *
import module
这给我们留下了一个名称 'module'
引用模块加载到的所有内存,以及一堆其他名称引用该块的各个部分。我们可以验证它们指向同一事物:
print(module.func_name == func_name)
# True
但是现在,我们尝试将其他内容分配给 module.attribute
:
module.func_name = lambda x:pass
print(module.func_name == func_name)
# False
不一样了。为什么?
嗯,当我们做module.func_name = lambda x:pass
的时候,我们首先分配了一些内存来存储lambda x:pass
,然后我们改变了module
的'func_name'
名称来引用那块内存而不是它所引用的内容。请注意,就像我之前给出的列表示例一样,我们没有更改 module.func_name
之前引用的内容 - 它仍然存在,并且本地 func_name
继续引用它。
因此,当您执行 from module import *
时,您是在 复制名称及其关联的引用 ;更改与原始名称关联的引用不会更改与副本关联的引用。
解决方法是不执行 import *
。事实上,这几乎就是 为什么 使用 import *
通常被认为是不良做法的最终原因,除了少数特殊情况。考虑以下代码:
# module.py
variable = "Original"
# file1.py
import module
def func1():
module.variable = "New"
# file2.py
import module
import file1
print(module.variable)
file1.func1()
print(module.variable)
当您 运行 python file2.py
时,您会得到以下输出:
Original
New
为什么?因为 file1
和 file2
都导入了 module
,并且在它们的两个命名空间中 'module'
都指向同一个内存块。 module
的命名空间包含引用某个值的名称 'variable'
。然后,会发生以下事情:
file2
说“好的,module
,请给我在你的命名空间中与名称 'variable'
关联的值。
file1.func1()
说“好的,module
,您命名空间中的名称 'variable'
现在引用了这个其他值。
file2
说“好的,module
,请给我在你的命名空间中与名称 'variable'
关联的值。
由于 file1
和 file2
仍在与同一位内存通信,因此它们保持协调。
背景故事:
我正在尝试实现一种处理 -v
参数的方法,以增加应用程序的冗长程度。为此,我想使用一个全局变量,它最初指向一个空的 lambda 函数。如果给定 -v
,则变量会更改并分配另一个 lambda 函数,该函数会打印它的输入。
MWE:
我注意到在通过 from x import *
...
mwe.py:
from mod import *
import mod
def f():
vprint("test in f")
vprint("test before")
print("before: %d" % foo)
set_verbosity(1)
vprint("test after")
print("after: %d" % foo)
f()
mod.vprint("explicit: %d" % mod.foo)
modf()
mod.py:
vprint = lambda *a, **k: None
foo = 42
def set_verbosity(verbose):
global vprint, foo
if verbose > 0:
vprint = lambda *args, **kwargs: print(*args, **kwargs)
foo = 0
def modf():
vprint("modf: %d" % foo)
输出为
before: 42
after: 42
explicit: 0
modf: 0
其中 "explicit" 和 "modf" 输出是由于 mwe 末尾的 mod.vprint
和 modf
调用。 vprint
的所有其他调用(通过 vprint
的导入版本)显然没有使用更新的定义。同样,foo
的值似乎只导入了一次。
问题:
在我看来,from x import *
类型的导入复制了导入模块的全局状态。我对解决方法本身不太感兴趣,但对这种行为的实际原因很感兴趣。这是在文档中的什么地方定义的?基本原理是什么?
解决方法: 作为旁注,无论如何实现这一点的一种方法是通过函数包装全局 lambda 变量并仅导出它们:
_vprint = lambda *a, **k: None
def vprint(*args, **kwargs):
_vprint(*args, **kwargs)
def set_verbosity(verbose):
global _vprint
if verbose > 0:
_vprint = lambda *args, **kwargs: print(*args, **kwargs)
这使得它与 import-from 方式一起工作,允许其他模块简单地调用 vprint
而不是通过模块名称显式引用。
前方黑夜中随机一刺:
If you look at the docs for __import__
,有一点:
On the other hand, the statement
from spam.ham import eggs, sausage as saus
results in_temp = __import__('spam.ham', globals(), locals(), ['eggs', 'sausage'], 0) eggs = _temp.eggs saus = _temp.sausage
我认为这是关键。如果我们推断 from mod import *
结果类似于:
_temp = __import__('mod', globals(), locals(), [], 0)
printv = _temp.printv
foo = _temp.foo
这表明问题所在。 printv
是对旧版本printv
的引用; mod.printv
在导入时指向的内容。重新分配 mod
中的 printv
指向的内容不会影响 mwe
中的任何内容,因为 mwe
对 printv
的引用仍在查看之前的 lambda .
这和这个不变的方式类似b
:
a = 1
b = a
a = 2
b
仍然指向 1
,因为重新分配 a
不会影响 b
正在查看的内容。
另一方面,mod.printv
确实有效,因为我们现在在mod
中使用对全局的直接引用而不是指向的引用mod
.
printv
这是一个随机刺探,因为我想我根据前一段时间的一些随机阅读知道了答案。如果我不正确,请告诉我,我会删除它以避免混淆。
TL;DR:当您执行 from module import *
时,您是在 复制名称及其相关引用 ;更改与原始名称关联的引用不会更改与副本关联的引用。
这处理了 names 和 references 之间的根本区别。这种行为的根本原因与 python 处理此类事情的方式有关。
在python中只有一件事是真正不变的:记忆。您不能直接更改单个字节。但是,python 中的几乎所有内容都处理 对 单个字节的引用,您可以更改这些引用。当您执行 my_list[2] = 5
时,您并没有更改任何内存 - 相反,您正在分配一个新的内存块来保存值 5
,并将 my_list
的第二个索引指向它。 my_list[2]
曾经指向的原始数据仍然存在,但由于不再引用它,垃圾收集器最终会处理它并释放它正在使用的内存。
同样的原则也适用于名字。 python 中的任何给定名称空间都与 dict
相当 - 每个名称都有相应的引用。这就是问题所在。
考虑以下两个语句之间的区别:
from module import *
import module
在这两种情况下,module
都被加载到内存中。
在后一种情况下,只有一件事被添加到本地命名空间——名称'module'
,它引用包含刚刚加载的模块的整个内存块。或者,嗯,它引用了该模块自己的命名空间的内存块,它本身引用了模块中的所有名称,依此类推。
然而,在前一种情况下,module
命名空间中的每个名称都被复制到本地命名空间中。同一个内存块仍然存在,但是现在 many[=] 不再是 one 对 all 的引用111=] 引用了它的 小部分 。
现在,假设我们连续执行这两个语句:
from module import *
import module
这给我们留下了一个名称 'module'
引用模块加载到的所有内存,以及一堆其他名称引用该块的各个部分。我们可以验证它们指向同一事物:
print(module.func_name == func_name)
# True
但是现在,我们尝试将其他内容分配给 module.attribute
:
module.func_name = lambda x:pass
print(module.func_name == func_name)
# False
不一样了。为什么?
嗯,当我们做module.func_name = lambda x:pass
的时候,我们首先分配了一些内存来存储lambda x:pass
,然后我们改变了module
的'func_name'
名称来引用那块内存而不是它所引用的内容。请注意,就像我之前给出的列表示例一样,我们没有更改 module.func_name
之前引用的内容 - 它仍然存在,并且本地 func_name
继续引用它。
因此,当您执行 from module import *
时,您是在 复制名称及其关联的引用 ;更改与原始名称关联的引用不会更改与副本关联的引用。
解决方法是不执行 import *
。事实上,这几乎就是 为什么 使用 import *
通常被认为是不良做法的最终原因,除了少数特殊情况。考虑以下代码:
# module.py
variable = "Original"
# file1.py
import module
def func1():
module.variable = "New"
# file2.py
import module
import file1
print(module.variable)
file1.func1()
print(module.variable)
当您 运行 python file2.py
时,您会得到以下输出:
Original
New
为什么?因为 file1
和 file2
都导入了 module
,并且在它们的两个命名空间中 'module'
都指向同一个内存块。 module
的命名空间包含引用某个值的名称 'variable'
。然后,会发生以下事情:
file2
说“好的,module
,请给我在你的命名空间中与名称'variable'
关联的值。file1.func1()
说“好的,module
,您命名空间中的名称'variable'
现在引用了这个其他值。file2
说“好的,module
,请给我在你的命名空间中与名称'variable'
关联的值。
由于 file1
和 file2
仍在与同一位内存通信,因此它们保持协调。