简体   繁体   English

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

[英]conditionally import module to shadow local implementation

I am writing a Python script where some of the core functionalities can be done by another existing library. 我正在编写一个Python脚本,其中某些核心功能可以由另一个现有库完成。 Unfortunately, while that library has more features, it is also slower, so I'd like if the user could at runtime select whether they want to use that library or my own fast and simple implementation. 不幸的是,尽管该库具有更多功能,但它也较慢,所以我想让用户在运行时可以选择是否要使用该库还是我自己的快速简单的实现。 Unfortunately I'm stuck at a point where I don't understand some of the workings of Python's module system. 不幸的是,我陷入了一个无法理解Python模块系统某些功能的地步。

Suppose that my main program was main.py , that the (optional) external module is in module_a.py and that my own fast and simple implementation of module_a together with the actual program code that uses either my own implementation or the one of module_a is in the file module_x.py : 假设我的主程序是main.py ,(可选的)外部模块在module_a.py ,并且我自己对module_a的快速简单实现以及使用我自己的实现或module_a的一个的实际程序代码是在module_x.py文件中:

main.py: main.py:

import module_x
module_x.test(True)
module_x.test(False)

module_a.py: module_a.py:

class myclass():
    def __init__(self):
        print("i'm myclass in module_a")

module_x.py: 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()

When I now execute main.py I get: 现在执行main.py我得到:

$ python3 main.py
i'm myclass in module_a
i'm myclass in module_a

But why is this? 但是为什么呢? If False is passed to test() then the import of the module_a implementation should never happen. 如果将False传递给test()则绝不应该导入module_a实现。 Instead it should only see myclass from the local file. 相反,它应该只从本地文件中看到myclass Why doesn't it? 为什么不呢? How do I make test() use the local definition of myclass conditionally? 如何使test()有条件地使用myclass的本地定义?

My solution is supposed to run in Python3 but I see the same effect when I use Python2.7. 我的解决方案应该在Python3中运行,但是当我使用Python2.7时会看到相同的效果。

An import statement is permanent within the thread of execution unless it is explicitly undone. 除非明确撤消,否则import语句在执行线程中是永久的。 Furthermore, once the from ... import statement is executed in this case, it replaces the variable myclass in the global scope (at which point the class it was previously referencing defined in the same file is no longer referenced and can in theory be garbage collected) 此外,在这种情况下执行了from ... import语句后,它将替换全局范围内的变量myclass (这时不再引用以前在同一文件中定义的类,因此从理论上讲可以是垃圾集)

So what is happening here is whenever you run test(True) the first time, your myclass in module_x is effectively deleted and replaced with the myclass from module_a . 那么,什么是这里发生的事情是当你运行test(True)的第一次,你的myclassmodule_x有效地删除并替换myclassmodule_a All subsequent calls to test(False) then call global myclass which is effectively a no-op since the global myclass now refers to the one imported from the other class (and besides the global call is unneeded when not changing the global variable from a local scope as explained here ). 随后所有对test(False)调用都将调用global myclass ,这实际上是一个无操作操作,因为全局myclass现在引用的是从另一个类导入的一个(此外,当不从本地更改全局变量时,不需要global调用)范围,因为解释这里 )。

To work around this, I would strongly suggest encapsulating the desired module-switching behavior in a class that is independent of either module you would like to switch. 要解决此问题,我强烈建议将所需的模块切换行为封装在一个类中,该类与您要切换的任一模块无关。 You can then charge that class with holding a reference to both modules and providing the rest of you client code with the correct one. 然后,您可以对该类进行收费,方法是保留对这两个模块的引用,并为其余的客户代码提供正确的代码。 Eg 例如

module_a_wrapper.py 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: main.py:

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

Running: 运行:

python main.py

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

You can read more about the guts of the python import system here 您可以在此处阅读有关python导入系统的更多信息

The answer by lemonhead properly explains why this effect happens and gives a valid solution. Lemonhead的答案正确地解释了为什么会发生这种影响并给出了有效的解决方案。

The general rule seems to be: wherever and however you import a module, it will always replace any variables of the same name from the global scope. 一般规则似乎是:无论在何处导入模块,它都将始终替换全局作用域中同名的任何变量。

Funnily, when I use the import foo as bar construct, then there must neither be a global variable named foo nor one named bar ! 有趣的是,当我使用import foo as bar构造时,既不能有一个名为foo的全局变量,也不能有一个名为bar的全局变量!

So while lemonhead's solution worked it adds lots of complexity and will lead to my code being much longer because every time I want to get something from either module I have to prefix that call with the getter function. 因此,尽管Lemonhead的解决方案有效,但它增加了很多复杂性,并且会导致我的代码更长,因为每次我想从这两个模块中获取任何东西时,都必须在该调用前加上getter函数。

This solution allows me to solve the problem with a minimal amount of changed code: 此解决方案使我可以用最少的更改代码来解决问题:

module_x.py: 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()

So the only thing I changed was to rename the class I had in global scope from myclass to myclass_fast . 因此,我唯一要做的就是将全局范围内的类从myclass重命名为myclass_fast This way it will not be overwritten anymore by the import of myclass from module_a . 这样,将不再被从module_a导入myclass module_a Then, on demand, I change the local variable myclass to either be the imported module or myclass_fast . 然后,根据需要,将局部变量myclass更改为导入的模块或myclass_fast

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM