有条件地导入模块以影响本地实现

conditionally import module to shadow local implementation

我正在编写一个 Python 脚本,其中一些核心功能可以由另一个现有库完成。不幸的是,虽然该库具有更多功能,但速度也较慢,所以我希望用户可以在 运行 时间 select 是否要使用该库或我自己的快速简单实现。不幸的是,我无法理解 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.

时我看到了相同的效果

import 语句在执行线程中永久,除非它被显式撤消。此外,在这种情况下,一旦执行了 from ... import 语句,它就会替换全局范围内的变量 myclass(此时它先前引用的在同一文件中定义的 class 不是引用时间更长,理论上可以进行垃圾回收)

所以这里发生的事情是当你第一次 运行 test(True) 时,你在 module_x 中的 myclass 被有效删除并替换为 myclass 来自 module_a。所有后续调用 test(False) 然后调用 global myclass 这实际上是一个空操作,因为全局 myclass 现在指的是从另一个 class 导入的那个(除了globalhere)所述,当不从局部范围更改全局变量时不需要调用。

要解决此问题,我强烈建议将所需的模块切换行为封装在一个 class 中,它独立于您要切换的任一模块。然后,您可以向 class 收取对这两个模块的引用,并为其余的客户端代码提供正确的代码。例如

module_a_wrapper.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()

main.py:

from module_a_wrapper import set_module, get_module
set_module(True)
get_module().myclass()
set_module(False)
get_module().myclass()

运行:

python main.py

# Outputs:
i'm myclass in module_a
i'm myclass in module_x

您可以阅读更多关于 python 导入系统的内容 here

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()

所以我唯一改变的是将我在全局范围内的 class 从 myclass 重命名为 myclass_fast。这样它就不会再被从 module_a 导入 myclass 覆盖。然后,根据需要,我将 local 变量 myclass 更改为导入模块或 myclass_fast.