仅当需要从模块中获取某个对象时,才如何触发另一个模块的后期导入?

时间:2019-05-31 18:16:41

标签: python python-import

情况

我想拥有一个大致如下所示的模块:

# my_module.py

my_number = 17

from other_module import foo
my_object = foo(23)

但是,存在一个问题:安装other_module会给某些用户带来麻烦,并且仅对那些想使用my_object的用户才需要-反过来,这又只是一小部分用户。我想让那些不需要my_object的用户免于安装other_module

因此,我希望仅从other_module导入my_object时才进行my_module的导入。换句话说,用户无需安装other_module就可以运行以下程序:

from my_module import my_number

到目前为止,我最好的解决方案

我可以通过包含导入的函数来提供my_object

# in my_module.py

def get_my_object():
    from other_module import foo
    my_object = foo(23)
    return my_object

然后,用户必须执行以下操作:

from my_module import get_my_object
my_object = get_my_object()

问题

是否有更好的方法来有条件地触发other_module的导入?我最感兴趣的是让用户保持简单。

3 个答案:

答案 0 :(得分:2)

我希望使用SliceConcatExt方法,但是从Python 3.7开始,通过定义module-level __getattr__ function可以实现您所要求的:

get_my_object()

只有在访问# my_module.py my_number = 17 def __getattr__(name): if name != 'my_object': raise AttributeError global my_object from other_module import foo my_object = foo(23) return my_object 后,这才会尝试导入other_module并调用foo。一些警告:

  • 通过my_object中的全局变量查找来尝试访问my_object的尝试将触发。只会在my_module个属性访问或my_module.my_object导入(在后台执行属性访问)时触发。
  • 如果您忘记为from my_module import my_object中的全局my_object分配名称,则__getattr__将在每次访问时重新计算。
  • 模块级my_object在Python 3.7之前不执行任何操作,因此您可能需要执行版本检查并对Python 3.6及以下版本执行其他操作:

    __getattr__

答案 1 :(得分:1)

方法A –干净的解决方案

创建一个新的单独模块,并让用户从另一个模块中导入对象。例如

from my_module import my_number # regular use
from my_module.extras import my_object # for a small part of the user base

这意味着在代码中,您将创建一个带有my_module的模块文件夹__init__.py,在其中您导入通常的内容,而不导入extras子模块。

如果您不想将extras放在my_module中(简单),只需在单独的my_object模块中创建extras.py

方法B – 99%的时间表示不良架构

您可以使用importlib.import_moduleget_my_object内动态导入模块而不会污染全局空间,并且比在函数内部进行导入更干净,该函数会产生诸如覆盖全局变量的副作用具有该导入名称(请参见this question and answers),但这通常 是代码其他部分编码模式错误的标志。

方法C –简单有效

当某些用户可能没有库时,我通常倾向于这种简单的模式,因为Python 3会阻止不在顶层的导入:

try:
    import other_module
    _HAS_OTHER_MODULE_ = True
except:
    _HAS_OTHER_MODULE_ = False


def get_my_object():
    assert _HAS_OTHER_MODULE_, "Please install other module"
    return other_module.foo(23)

答案 2 :(得分:1)

这是一个常见的技巧:

import sys

class MockModule:
    def __init__(self, module):
        self.module = module

    def __getattr__(self, attr):
        if attr == 'dependency_required_var':
            try:
                import foo
                return self.module.dependency_required_var
            except ImportError:
                raise Exception('foo library is required to use dependency_required_var')
        else:
            return getattr(self.module, attr)


MockModule.__name__ = __name__
sys.modules[__name__] = MockModule(sys.modules[__name__])

dependency_required_var = 0

使用this PEP,我们可以在Python 3.7及更高版本中简单地执行以下操作(应该可以,但我无法使它工作):

def __getattr__(attr):
    if attr == 'dependency_required_var':
        try:
            import foo
            return dependency_required_var
        except ImportError:
            raise Exception('foo library is required to use dependency_required_var')
    else:
        return globals()[attr]

PEP似乎已被接受,但是来自PEP的相关拉取请求似乎已关闭,我实际上不确定它是否已实现。