我有一个微妙的导入问题,它创建的变量引用的对象与我期望的不同,这完全取决于我正在编写的模块是“按原样”导入还是作为包的一部分导入。
假设我有一个定义
Config
类和设置它的函数的简单模块:
class Config:
def __init__(self, **kwargs):
if kwargs.get("print_keys", None):
self.print_keys = kwargs.pop("print_keys")
else:
self.print_keys = ["centre", "shortName"]
rcParams = Config()
def set_config(config=None):
global rcParams
print("Before change:", id(rcParams))
if config is None:
config = Config()
rcParams = config
print("After change:", id(rcParams))
如果我将其保存在名为
simple.py
的模块中,那么我可以使用此模块成功测试它:
# import gribtool as gt # note this line is uncommented later
import simple as gt # note this line is commented later
def test_set_config():
config = gt.Config(print_keys=["centre"])
print("Initial rcParams id:", id(gt.rcParams))
gt.set_config(config=config)
print("Updated rcParams id:", id(gt.rcParams))
assert gt.rcParams.print_keys == ["centre"]
test_set_config()
输出为:
Initial rcParams id: 128205347716160
Before change: 128205347716160
After change: 128205347716832
Updated rcParams id: 128205347716832
到目前为止一切顺利。现在的问题是我希望它成为一个包的一部分。我已将此模块复制(实际上是链接)到名为
gribtool
的包(目录)中,然后创建具有预期内容的
__init__.py
文件:
from .simple import *
目录内容如下:
├── gribtool
│ ├── __init__.py
│ └── simple.py -> ../simple.py
├── simply.py
└── test.py
然后,我在上面的测试模块中取消注释了带有
import gribtool as gt
的行,然后它失败并显示以下输出:
Initial rcParams id: 134251413862864
Before change: 134251413862864
After change: 134251413864064
Updated rcParams id: 134251413862864
Traceback (most recent call last):
File "/home/navarro/AEMET/GRIB_TOOL/test_config.py", line 13, in <module>
test_set_config()
File "/home/navarro/AEMET/GRIB_TOOL/test_config.py", line 10, in test_set_config
assert gt.rcParams.print_keys == ["centre"]
AssertionError
如您所见,问题是
rcParams
在
set_config
函数中被修改的
是
与
simple
模块中的不同
global
尽管有
指令。
遇到的问题与 Python 导入系统及其处理模块级全局变量的方式有关。让我们分解一下发生的事情:
场景 1:直接导入模块
当像这样导入
simple
模块时:
import simple as gt
实际上是创建了模块
simple
的一个副本,并将其命名为
gt
。因此,
gt.rcParams
和
simple.rcParams
都指向内存中的同一个对象。当调用
gt.set_config()
时,它会修改
gt.rcParams
,这反过来也修改了
simple.rcParams
,因为它们是同一个对象。
场景 2:从包导入
但是,当将
simple.py
放在一个包中并像这样导入时:
import gribtool as gt
情况就不同了。现在,
gt
指向的是包
gribtool
,而
gt.rcParams
实际上是
gribtool.rcParams
。当
__init__.py
文件执行
from .simple import *
时,它确实将
simple.rcParams
的值复制到了
gribtool.rcParams
中。
但是
,这仅仅是一个值的复制,而不是对原始对象的引用。
因此,当调用
gt.set_config()
时,它修改的是
gribtool.rcParams
,而不是
simple.rcParams
。这解释了为什么看到
id(gt.rcParams)
在调用
set_config()
后没有改变。
解决方案
为了解决这个问题,需要确保
set_config
函数修改的是
simple
模块中的
rcParams
对象,即使它是从包中导入的。可以通过在
set_config
函数中显式地导入
simple
模块来实现这一点:
def set_config(config=None):
import simple # Explicitly import the 'simple' module
print("Before change:", id(simple.rcParams))
if config is None:
config = Config()
simple.rcParams = config
print("After change:", id(simple.rcParams))
通过这样做,无论是直接导入
simple
模块还是从包中导入,
set_config
函数都会修改同一个
rcParams
对象。
其他注意事项
- 尽量避免使用模块级全局变量,尤其是在处理可变对象(如列表或字典)时。这可能会导致难以调试的问题。
- 考虑使用类来封装的配置,并使用类方法来修改配置。这将提供更好的封装性和可维护性。
希望这可以帮助理解问题的原因并提供解决方案!
标签:python,import,python-packaging From: 78773672