9

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

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

我正在考虑__init__.pyservicelibrary.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

然后当用户导入库时

from servicelibrary.simple import Publisher

底层看起来像这样

替代.py

import amqp

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

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

同步的.py

import pika

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

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

当第一个未安装时,这将自动选择第二个。

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

[编辑]

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

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

[编辑2]

原始示例已简化。每个模块可能包含多种类型的导入,例如 Consumer 和 Publisher。

4

5 回答 5

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

于 2013-10-23T11:49:51.240 回答
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。

如果您还有更多问题,请随时发表评论和提问!

于 2013-11-01T16:43:52.403 回答
1

你有正确的想法。您的案例有效,因为每个子对象都具有相同类型的类,例如,两个 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

等等

于 2013-10-29T19:01:38.170 回答
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在其他文件中使用。

第二个,这是我的猜测,我写了简单的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我的猜测是什么。

于 2013-11-01T14:25:37.227 回答
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.')
于 2013-11-18T09:49:26.970 回答