有条件映射的模块导入
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
capabilities 或 setuptools
。因此,在您的具体实现之一的 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。
我目前正在为 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
capabilities 或 setuptools
。因此,在您的具体实现之一的 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。