我正在编写一个Python脚本,其中一些核心功能可以由另一个现有的库完成。不幸的是,虽然该库具有更多功能,但速度也较慢,所以我想如果用户可以在运行时选择是否要使用该库或我自己的快速简单实现。不幸的是,我已经陷入了无法理解Python模块系统的一些工作的地步。
假设我的主程序是main.py
,(可选的)外部模块在module_a.py
中,我自己快速而简单地实现module_a
以及实际的程序代码使用我自己的实现或module_a
文件module_x.py
:
main.py:
import module_x
module_x.test(True)
module_x.test(False)
module_a.py:
class myclass():
def __init__(self):
print("i'm myclass in module_a")
module_x.py:
class myclass():
def __init__(self):
print("i'm myclass in module_x")
def test(enable_a):
if enable_a:
try:
from module_a import myclass
except ImportError:
global myclass
enable_a = False
else:
global myclass
i = myclass()
当我现在执行main.py
时,我得到:
$ python3 main.py
i'm myclass in module_a
i'm myclass in module_a
但为什么会这样呢?如果将False
传递给test()
,则永远不会导致module_a
实施的导入。相反,它应该只从本地文件中看到myclass
。为什么不呢?如何让test()
有条件地使用myclass
的本地定义?
我的解决方案应该在Python3中运行,但是当我使用Python2.7时,我看到了相同的效果。
答案 0 :(得分:1)
import
语句在执行的线程中是永久,除非它被明确撤消。此外,在这种情况下执行from ... import
语句后,它将替换全局范围中的变量myclass
(此时不再引用先前在同一文件中定义的引用的类,并且理论上是垃圾收集)
所以这里发生的事情是,无论何时第一次运行test(True)
,您myclass
中的module_x
都会被有效删除,并替换为来自myclass
的{{1}} }。所有后续调用module_a
然后调用test(False)
实际上是无操作,因为全局myclass现在引用从另一个类导入的那个(除了global myclass
调用之外是不需要的不按照here所解释的那样从本地范围更改全局变量。
要解决这个问题,我强烈建议将所需的模块切换行为封装在一个独立于您要切换的模块的类中。然后,您可以通过持有对两个模块的引用并为其余的客户端代码提供正确的模块来为该类收费。 E.g。
module_a_wrapper.py
global
main.py:
import module_x
import module_a
class ModuleAWrapper(object):
_target_module = module_x # the default
@classmethod
def get_module(cls):
return cls._target_module
def set_module(enable_a):
if enable_a:
ModuleAWrapper._target_module = module_a
else:
ModuleAWrapper._target_module = module_x
def get_module():
return ModuleAWrapper.get_module()
运行:
from module_a_wrapper import set_module, get_module
set_module(True)
get_module().myclass()
set_module(False)
get_module().myclass()
您可以阅读有关python导入系统here
的内容的更多信息答案 1 :(得分:0)
lemonhead的回答恰当地解释了为什么会出现这种效应并提供有效的解决方案。
一般规则似乎是:无论在何处导入模块,它都将始终从全局范围中替换同名的任何变量。
有趣的是,当我使用import foo as bar
构造时,必须既没有名为foo
的全局变量,也没有名为bar
的全局变量!
因此,虽然lemonhead的解决方案有效,但它会增加很多复杂性,并且会导致我的代码更长,因为每次我想从任一模块获取内容时,我都必须使用getter函数为该调用添加前缀。
此解决方案允许我用最少量的更改代码解决问题:
module_x.py:
class myclass_fast():
def __init__(self):
print("i'm myclass in module_x")
def test(enable_a):
if enable_a:
try:
from module_a import myclass
except ImportError:
enable_a = False
myclass = myclass_fast
else:
myclass = myclass_fast
i = myclass()
所以我改变的唯一方法是将我在全球范围内的班级从myclass
重命名为myclass_fast
。这样,从myclass
导入module_a
就不会覆盖它。然后,根据需要,我将本地变量myclass
更改为导入的模块或myclass_fast
。