有条件映射的模块导入

Conditionally mapped module import

我目前正在为 Python 2.7 开发一组抽象模块,我将把它们作为 python 包发布:

 myabstractpkg
   - abstract
      - core
       - logging
       - ...
     - nodes
     - ...

这些模块将在一组完全不同的包中实现:

 myimppkg
   - implementation
     - core
       - logging
       - ...
     - nodes
     - ...

然而,在运行时,我希望能够在使用已实现模块的工具中始终像这样导入:

from myabstractpkg.api import nodes
from myabstractpkg.api.core.logging import Logger

这样开发人员总是从 "virtual" api 模块导入,然后由该模块决定实际指向导入器的位置。

我知道我可以通过修改模块 dict 以某种方式将它组合在一起:

from myimppkg import implementation
sys.modules["myabstractpkg.api"] = implementation

或者对 myabstractpackage.api__init__.py 中的所有内容进行巧妙的导入,但这对我来说感觉有点脆弱。

我想知道你们是否对最好的方法有什么意见。对于整个重新映射模块的事情,我可能会走上一条非常丑陋的轨道,所以如果你们有任何更聪明、更 pythonic 的解决方案,对于我的 API 抽象、实现、使用方法,我很乐意听到他们。

我相信您最好使用 entry_points capabilitiessetuptools。因此,在您的具体实现之一的 setup.py 中,您可以这样定义 entry_points

setup(
    name="concrete_extension"
    entry_points={
        "abstract_pkg_extensions": [
            "concrete = concrete_extension"
        ]
    }
}

然后你可以在你的抽象包中有一个扩展模块,它做这样的事情:

import pkg_resources
import os
import sys

from . import default

extensions = { "default": default }
extensions.update({e.name: e.load() for e in 
pkg_resources.iter_entry_points("my_pkg_extensions")})

current_implementation_name = None
current_implementation = None

def set_implementation(name):
    global current_implementation_name, current_implementation
    try:
        current_implementation = extensions[name]
        current_implementation_name = name

        # allow imports like from foo.current_implementation.bar import baz
        sys.modules["{}.current_implementation".format(__name__)] = current_implementation
        # here is where you would do any additional stuff
        # -- e.g. --
        # from . import logger
        # logger.Logger = current_implementation.Logger
    except KeyError:
        raise NotImplementedError("No implementation for: {}".format(name))

set_implementation(os.environ.get("CURRENT_IMPLEMENTATION_NAME", "default"))

然后您可以使用 ext.current_implementation 访问当前实现,并在导入之前在程序环境中设置您的实现,或者您可以在导入任何子代码之前在代码中显式调用 set_implementation - 使用 ext.current_implementation.

的模块

有关 sys.modules 条目的更多信息及其工作原理,请参阅 this question