0

情况

我想要一个大致如下工作的模块:

# 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_objectmy_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?我最感兴趣的是让用户的事情尽可能简单。

4

3 回答 3

2

我更喜欢这种get_my_object()方法,但从 Python 3.7 开始,您可以通过定义模块级__getattr__函数来实现您的要求:

# 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_modulefoo仅在访问一次时my_object调用。一些警告:

  • 不会触发my_objectmy_module. 它只会在my_module.my_object属性访问或from my_module import my_object导入(在后台执行属性访问)时触发。
  • 如果您忘记在 中分配全局my_object名称__getattr__my_object将在每次访问时重新计算。
  • 模块级别__getattr__在 Python 3.7 之前什么都不做,因此您可能希望执行版本检查并为 Python 3.6 及以下版本做一些其他事情:

    import sys
    if sys.version_info >= (3, 7):
        def __getattr__(name):
            ...
    else:
        # Do something appropriate. Maybe raise an error. Maybe unconditionally
        # import other_module and compute my_object up front.
    
于 2019-05-31T18:51:45.660 回答
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子模块的位置。

如果您不想放入extrasmy_module更简单),只需my_object在单个extras.py模块中创建即可。

方法 B – 99% 的情况表明架构不好

您可以使用importlib.import_module在内部动态导入模块get_my_object而不会污染全局空间,这比在函数内部导入更干净,这会产生副作用,例如用该导入名称覆盖全局变量(请参阅此问题和答案),但这通常是代码其他部分的错误编码模式的标志。

方法 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)
于 2019-05-31T18:23:48.677 回答
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

有了这个 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 的相关拉取请求似乎已关闭,我实际上不确定它是否已实施。

于 2019-05-31T19:03:41.093 回答