根据安装的库选择动态选择适配器

时间:2013-10-23 11:32:55

标签: python python-2.7 module adapter libraries

我正在设计一个具有支持各种库的适配器的库。我希望库在导入特定类时动态选择哪个适配器具有在机器上安装的库。

目标是能够更改程序所依赖的库,而无需对代码进行修改。这个特殊功能用于处理RabbitMQ连接,因为我们在pika遇到了很多问题,我们希望能够更改为不同的库,例如pyAMPQrabbitpy,无需更改基础代码。

我正在考虑在__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]

原始示例已简化。每个模块可以包含多种类型的导入,例如,消费者和出版商。

5 个答案:

答案 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.')