【问题标题】:Choose adapter dynamically depending on librarie(s) installed根据安装的库动态选择适配器
【发布时间】:2013-10-23 11:32:54
【问题描述】:

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

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

我正在考虑在servicelibrary.simple__init__.py 文件中实现类似的东西。

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

那么当用户导入库时

from servicelibrary.simple import Publisher

底层看起来像这样

alternative.py

import amqp

class Publisher(object):
    ......

class Consumer(object):
     ......    

同步.py

import pika

class Publisher(object):
    ......

class Consumer(object):
     ......   

当第一个没有安装时,这会自动选择第二个。

有没有更好的方法来实现这样的事情?如果有人可以将库/适配器与类似的实现链接起来,那也会很有帮助。

[编辑]

实现这样的东西最干净的方法是什么?将来我还希望能够更改默认首选项。最终我可能会满足于使用安装的库,因为我可以控制它,但它会是一个很好的功能。

Alexanders 的建议很有趣,但我想知道是否有更清洁的方法。

[编辑2]

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

【问题讨论】:

  • 查看一个组件架构,例如 zope.component。

标签: python python-2.7 module adapter libraries


【解决方案1】:

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 模块。

【讨论】:

  • 我想我可能仍然会这样做,但我想看看是否有更清洁的方式来实现这一点。虽然,有了这个,我可以通过调整列表来更改首选库,这很好。
  • 我基于此发布了我的版本。我对结果很满意。
【解决方案2】:

使用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。

如果您还有其他问题,请随时评论和提问!

【讨论】:

  • 谢谢。我最终选择了基于此的解决方案。
【解决方案3】:

你的想法是对的。您的案例有效,因为每个子对象都具有相同类型的类,例如这两个 API 都有一个名为 Publisher 的类,您只需确保导入了正确的版本即可。

如果不是这样(如果可能的实现 A 和 B 不相似),您编写自己的外观,这只是您自己的简单 API,然后使用该库的正确方法/参数调用真正的 API。

显然,在选择之间切换可能需要一些开销(我不知道您的情况,但是例如,假设您有两个库来遍历打开的文件,并且库处理打开文件。您不能只切换到文件中间的第二个库,并期望它从第一个库停止的地方开始)。但这只是保存它的问题:

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

等等。

【讨论】:

  • 感谢您的回答。明天我会尝试实现这一点。基本上,导入的实现是我编写的层,以确保无论使用什么库,它的行为都是相同的。所以每个 Publisher 都将拥有完全相同的公共变量、函数等。
【解决方案4】:

我知道两种方法,一种被广泛使用,另一种是我的猜测。您可以根据自己的情况选择一种。

第一种,使用比较广泛,比如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

第二个,这是我的猜测,我写了一个简单的demo,但我没有在生产环境中使用它,因为我不需要它。

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 是什么。

【讨论】:

    【解决方案5】:

    根据答案,我最终得到了 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.')
    

    【讨论】:

    • 刚刚注意到这一点,并想指出这似乎错过了 Python 的一个独特功能——try 和 for 语句中的 else 子句(呸,cmets 中没有代码格式)。如果没有抛出异常,try 中的“else”会被执行,如果没有中断,for 中的“else”会被执行;您可以使用它来消除 SUCCESS 变量。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-06
    • 1970-01-01
    • 2019-04-06
    • 1970-01-01
    • 2012-09-08
    • 2011-01-04
    相关资源
    最近更新 更多