如何正确处理python中的可选功能

Jer*_*emy 5 python python-3.x

我正在研究实现科学模型的python软件包,我想知道什么是处理可选功能的最佳方法。这是我想要的行为:如果无法导入一些可选的依赖项(例如,在无头计算机上绘制模块),我想在我的类中禁用使用这些模块的功能,并警告用户如果他尝试使用它们以及所有这些,而不会破坏执行力。因此以下脚本在任何情况下均适用:

mymodel.dostuff()
mymodel.plot() <= only plots if possible, else display log an error 
mymodel.domorestuff() <= get executed regardless of the result of the previous statement
Run Code Online (Sandbox Code Playgroud)

到目前为止,我看到的选项如下:

  • 检入__init __.py可用模块并保留它们的列表(但是如何在包装的其余部分中正确使用它?)
  • 对于依赖于可选依赖项的每个函数,都有一条try import ... except ...语句
  • 将功能取决于特定模块放在单独的文件中

这些选项应该可以使用,但是它们似乎都非常笨拙且难以维护。如果我们想完全删除依赖项怎么办?还是强制性?

jme*_*jme 5

当然,最简单的解决方案是将可选依赖项简单地导入需要它们的函数主体中。但是永远正确的PEP 8说:

导入总是放在文件的顶部,紧随任何模块注释和文档字符串之后,以及模块全局变量和常量之前。

我不想违背python大师的最好祝愿,我采取以下方法,这有很多好处...

首先,使用try-except导入

说我的功能foo需求之一numpy,而我想使其成为可选的依赖项。在模块的顶部,我输入:

try:
    import numpy as _numpy
except ImportError:
    _has_numpy = False
else:
    _has_numpy = True
Run Code Online (Sandbox Code Playgroud)

此处(在except块中)将是打印警告的位置,最好使用warnings模块。

然后在函数中抛出异常

如果用户打电话foo并且没有numpy怎么办?我在那里扔了例外,并记录了这种行为。

def foo(x):
    """Requires numpy."""
    if not _has_numpy:
        raise ImportError("numpy is required to do this.")
    ...
Run Code Online (Sandbox Code Playgroud)

另外,您可以使用装饰器并将其应用于需要该依赖项的任何函数:

@requires_numpy
def foo(x):
    ...
Run Code Online (Sandbox Code Playgroud)

这样做的好处是可以防止代码重复。

并将其添加为安装脚本的可选依赖项

如果要分发代码,请查看如何向安装配置添加额外的依赖关系。例如,使用setuptools,我可以写:

install_requires = ["networkx"],

extras_require = {
    "numpy": ["numpy"],
    "sklearn": ["scikit-learn"]}
Run Code Online (Sandbox Code Playgroud)

这指定networkx在安装时绝对需要,但是模块的额外功能需要numpysklearn,它们是可选的。


使用此方法,以下是您的特定问题的答案:

  • 如果我们想强制依赖项怎么办?

我们可以简单地将可选依赖项添加到设置工具的必需依赖项列表中。在上面的例子中,我们移动numpyinstall_requiresnumpy然后可以删除所有检查是否存在的代码,但是将其保留不会导致程序中断。

  • 如果我们想完全删除依赖项怎么办?

只需删除以前需要它的任何函数中的依赖项检查即可。如果您使用装饰器实现了依赖项检查,则可以对其进行更改,以使它简单地将原始函数传递给原先的函数。

这种方法的好处是将所有导入都放在模块的顶部,这样我就可以一眼看出什么是必需的,什么是可选的。

  • “try-import”是在 Python 中执行可选依赖项的 100% 正确方法。我自己已经用过很多次了,而且在野外也很常见。它比将导入隐藏在函数内更具可读性。+1 (2认同)