Choose adapter dynamically depending on librarie(s) installed
我正在设计一个库,该库具有支持各种库的适配器。我希望库在导入特定类时动态选择在计算机上安装了它使用的库的适配器。
目标是能够更改程序所依赖的库,而不必修改代码。此特定功能用于处理rabbitmq连接,因为我们在PIKA中遇到了很多问题,所以我们希望能够在不必更改基础代码的情况下更改到其他库,例如pyampq或rabbitpy。
我正在考虑在
1 2 3 4 5 6 7 8 | 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 |
然后当用户导入库时
1 | from servicelibrary.simple import Publisher |
底层看起来像这样
可替代的
1 2 3 4 5 6 7 | import amqp class Publisher(object): ...... class Consumer(object): ...... |
同步Py
1 2 3 4 5 6 7 | import pika class Publisher(object): ...... class Consumer(object): ...... |
这将在第一个未安装时自动选择第二个。
有没有更好的方法来实现这样的事情?如果有人可以将库/适配器与类似的实现链接起来,这也会很有帮助。
[编辑]
什么是实现这种功能最干净的方法?将来,我还希望能够更改默认首选项。最终,我可能会选择使用安装的库,因为我可以控制它,但这将是一个很好的特性。
亚历山德斯的建议很有趣,但我想知道是否有一个更清洁的方法。
[编辑2]
原来的例子被简化了。每个模块可以包含多种类型的导入,例如使用者和发布者。
灵活的解决方案,使用
首先,标题:
1 2 3 4 5 | import importlib parent = 'servicelib.simple' modules = {'.synchronous':['.alternative', '.alternative_2']} success = False #an indicator, default is False, #changed to True when the import succeeds. |
我们导入所需的模块,设置指示器,并指定模块。
接下来,导入Ant部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #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 |
要想上这些课,只需:
1 2 | Publisher = mod.Publisher Consumer = mod.Consumer |
有了这个解决方案,您可以同时拥有多个备选方案。例如,您可以同时使用rabbitpy和pyampq作为备选方案。
注意:适用于python 2和python 3。
如果您有更多问题,请随时发表评论和提问!
importlib.import_模块可以执行您需要的操作:
1 2 3 4 5 6 7 8 9 10 11 12 | 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模块。
基于这些答案,我最终得到了下面的Python2.7实现。
StackOverflow的示例被简化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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。生产平台的问题是,包含新的依赖项并不总是容易的。
这是我提出的折衷方案,基于
可能需要检查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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.') |
我知道两种方法,一种被广泛使用,另一种是我的猜测。你可以根据自己的情况选择一个。
第一种,广泛使用,如
1 2 3 4 5 6 7 8 9 10 11 | try: from concurrent import futures except ImportError: futures = None #define _DummyFuture balabala... if futures is None: Future = _DummyFuture else: Future = futures.Future |
然后您可以在其他文件中使用
第二个,这是我的猜测,我写了简单的演示,但我没有在生产环境中使用它,因为我不需要它。
1 2 3 4 5 6 | import sys try: import servicelibrary.simple.synchronous except ImportError: import servicelibrary.simple.alternative sys.modules['servicelibrary.simple.synchronous'] = servicelibrary.simple.alternative |
您可以在其他脚本
1 2 | from servicelibrary.simple.synchronous import Publisher from servicelibrary.simple.synchronous import Consumer |
我唯一想知道的是,我的猜测是什么。
你的想法是对的。因为每个子对象都有相同类型的类,例如,两个API都有一个名为
如果这不是真的(如果可能的话,实现A和B不相似),您可以编写自己的Facade,它只是您自己的简单API,然后使用该库的正确方法/参数调用真正的API。
显然,在选择之间切换可能需要一些开销(我不知道您的情况,但例如,假设您有两个库来遍历打开的文件,库处理打开的文件。您不能只切换到文件中间的第二个库,并期望它从第一个库停止的地方开始)。但这只是拯救它的问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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 |
等。