我正在设计一个具有支持各种库的适配器的库。我希望库在导入特定类时动态选择哪个适配器具有在机器上安装的库。
目标是能够更改程序所依赖的库,而无需对代码进行修改。这个特殊功能用于处理RabbitMQ连接,因为我们在pika遇到了很多问题,我们希望能够更改为不同的库,例如pyAMPQ或rabbitpy,无需更改基础代码。
我正在考虑在__init__.py
的{{1}}文件中实现类似的内容。
servicelibrary.simple
然后当用户导入库
时try:
#import pika # Is pika installed?
from servicelibrary.simple.synchronous import Publisher
from servicelibrary.simple.synchronous import Consumer
except ImportError:
#import ampq # Is ampq installed?
from servicelibrary.simple.alternative import Publisher
from servicelibrary.simple.alternative import Consumer
底层看起来像这样
alternative.py
from servicelibrary.simple import Publisher
synchronous.py
import amqp
class Publisher(object):
......
class Consumer(object):
......
当未安装第一个时,这将自动选择第二个。
有没有更好的方法来实现这样的东西?如果有人可以将库/适配器与类似的实现链接起来也是有用的。
[编辑]
实施这样的事情最简洁的方法是什么?在将来,我还希望能够更改默认首选项。最终我可能只是满足于使用安装的库,因为我可以控制它,但它将是一个很好的功能。
Alexanders的建议很有意思,但我想知道是否有更清洁的方式。
[EDIT2]
原始示例已简化。每个模块可以包含多种类型的导入,例如,消费者和出版商。
答案 0 :(得分:3)
importlib.import_module可能会满足您的需求:
INSTALLED = ['syncronous', 'alternative']
for mod_name in INSTALLED:
try:
module = importlib.import_module('servicelibrary.simple.' + mod_name)
Publisher = getattr(module, 'Publisher')
if Publisher:
break # found, what we needed
except ImportError:
continue
我想,这不是最先进的技术,但这个想法应该是明确的。 您也可以查看imp模块。
答案 1 :(得分:3)
灵活的解决方案,使用importlib
。这是我测试过的完整,有效的解决方案。
首先,标题:
import importlib
parent = 'servicelib.simple'
modules = {'.synchronous':['.alternative', '.alternative_2']}
success = False #an indicator, default is False,
#changed to True when the import succeeds.
我们导入所需的模块,设置指标,并指定我们的模块。 modules
是一个字典,键设置为默认模块,值为备选列表。
接下来,import-ant部分:
#Obtain the module
for default, alternatives in modules.items():
try: #we will try to import the default module first
mod = importlib.import_module(parent+default)
success = True
except ImportError: #the default module fails, try the alternatives
for alt in alternatives:
try: #try the first alternative, if it still fails, try the next one.
mod = importlib.import_module(parent+alt)
success = True
#Stop searching for alternatives!
break
except ImportError:
continue
print 'Success: ', success
要获得课程,只需:
Publisher = mod.Publisher
Consumer = mod.Consumer
使用此解决方案,您可以同时拥有多个备选方案。例如,您可以使用rabbitpy和pyAMPQ作为替代方案。
注意:适用于Python 2和Python 3。
如果您有更多问题,请随时发表评论并提出要求!
答案 2 :(得分:1)
你有正确的想法。您的案例有效,因为每个子对象具有相同类别,例如这两个API都有一个名为Publisher
的类,您可以确保导入正确的版本。
如果不是这样(如果可能的实现A和B不相似),您可以编写自己的外观,这只是您自己的简单API,然后使用该库的正确方法/参数调用真实API。 / p>
显然,在选项之间切换可能需要一些开销(我不知道你的情况,但是,例如,假设你有两个库来浏览一个打开的文件,并且库处理打开文件。你不能只是切换到文件中间的第二个库,并期望它从第一个库停止的地方开始)。但这只是拯救它的问题:
accessmethods = {}
try:
from modA.modB import classX as apiA_classX
from modA.modB import classY as apiA_classY
accessmethods['apiA'] = [apiA_classX, apiA_classY]
classX = apiA_classX
classY = apiA_classY
except:
pass
try:
from modC.modD import classX as apiB_classX
from modC.modD import classY as apiB_classY
accessmethods['apiB'] = [apiB_classX, apiB_classY]
classX = apiB_classX
classY = apiB_classY
except:
pass
def switchMethod(method):
global classX
global classY
try:
classX, classY = accessmethods[method]
except KeyError as e:
raise ValueError, 'Method %s not currently available'%method
等。
答案 3 :(得分:1)
我知道两种方法,一种是疯狂使用,另一种是我的猜测。您可以根据自己的情况选择一个。
第一个被广泛使用的,例如from tornado.concurrent import Future
。
try:
from concurrent import futures
except ImportError:
futures = None
#define _DummyFuture balabala...
if futures is None:
Future = _DummyFuture
else:
Future = futures.Future
然后您可以在其他文件中使用from tornado.concurrent import Future
。
第二个,这是我的猜测,我写了简单的演示,但我没有在生产环境中使用它,因为我不需要它。
import sys
try:
import servicelibrary.simple.synchronous
except ImportError:
import servicelibrary.simple.alternative
sys.modules['servicelibrary.simple.synchronous'] = servicelibrary.simple.alternative
您可以在其他脚本import servicelibrary.simple.synchronous
之前运行该脚本。然后您可以像以前一样使用脚本:
from servicelibrary.simple.synchronous import Publisher
from servicelibrary.simple.synchronous import Consumer
我唯一想知道的是我猜测的consequences
是什么。
答案 4 :(得分:1)
根据答案,我最终得到了Python 2.7的以下实现。
stackoverflow简化了示例。。
from importlib import import_module
PARENT = 'myservicelib.rabbitmq'
MODULES = ['test_adapter',
'test_two_adapter']
SUCCESS = False
for _module in MODULES:
try:
__module = import_module('{0}.{1}'.format(PARENT, _module))
Consumer = getattr(__module, 'Consumer')
Publisher = getattr(__module, 'Publisher')
SUCCESS = True
break
except ImportError:
pass
if not SUCCESS:
raise NotImplementedError('no supported rabbitmq library installed.')
虽然我也有一些运行Python 2.6的项目,但我必须修改代码,或者包含importlib。生产平台的问题在于包含新的依赖项并不总是容易的。
这是我提出的妥协,基于__import__
而不是importlib
。
可能值得检查sys.modules
是否实际包含命名空间,因此您不会引发KeyError
,但不太可能。
import sys
PARENT = 'myservicelib.rabbitmq'
MODULES = ['test_adapter',
'test_two_adapter']
SUCCESS = False
for _module in MODULES:
try:
__module_namespace = '{0}.{1}'.format(PARENT, _module)
__import__(__module_namespace)
__module = sys.modules[__module_namespace]
Consumer = getattr(__module, 'Consumer')
Publisher = getattr(__module, 'Publisher')
SUCCESS = True
break
except ImportError:
pass
if not SUCCESS:
raise NotImplementedError('no supported rabbitmq library installed.')