使用内联导入可能有什么负面影响吗?

2 投票
4 回答
2373 浏览
提问于 2025-04-17 15:40

我正在开发一个Python程序,希望即使缺少某些库,它也能正常运行。这些库是程序某些功能所需要的。(编辑:我写了一些代码来实现最佳建议的解决方案,代码可以在这里找到,测试代码在这里。)

我通过把需要的库的导入语句放在使用它们的函数内部,而不是在Python文件的顶部,来解决这个问题。这意味着即使你没有这些库,文件也能正常加载,不过如果你尝试调用其中一个函数,就会出现导入错误(ImportError)。

这个方法效果很好,以至于我有时也会对标准库模块这样做——但现在我在想,这样做会不会有什么隐藏的成本呢?

基础代码:

import numpy

def foo(): 
  return numpy.array([])

def bar(): 
  return numpy.array([1, 2, 3])

带有内联导入的代码:

def foo(): 
  import numpy
  return numpy.array([])

def bar(): 
  import numpy
  return numpy.array([1, 2, 3])

编辑:

我完全同意不应该对标准库代码进行内联导入——这显然是不好的做法。

我现在认为,使用受保护的导入是正确的解决方案。

特别是,我对调用进行了时间测试,虽然对于大多数应用来说,时间差异可能不大,但还是能感受到(我知道这是个微妙的界限!)

在一个简单的情况下:

import numpy
def f():
  return numpy

在我的机器上,执行100,000次大约需要180毫秒,而

def f():
  import numpy
  return numpy

则大约需要870毫秒。

粗略的结论是,这样的成本相当于四次简单函数调用——在大多数情况下是明显的,但并不算重要。不过,如果这样做不会给你带来任何成本,最好还是避免。

在实验中,我还意识到内联导入的另一个缺点——这些导入是在函数被调用时以不可预测的时间发生的。在我的应用中,涉及实时元素,这种情况是不可接受的。

4 个回答

1

其实不是这样的。

导入操作只会发生一次,但可能在用户意想不到的时刻发生,也就是当调用那个进行导入的函数时,第一次才会执行。

另外,这也是为了代码的可读性——如果你按照惯例把导入放在最上面,任何阅读你代码的人都能立刻知道它依赖了哪些东西。如果导入发生在你模块的第284行,这种清晰度可能就会丢失了……

3

你这样做是不符合PEP 8规范的。对于标准库的导入,你没有充分的理由这样做,这样做是双重错误,可能会让一些人不愿意使用你的代码(或者至少会礼貌地提醒你不要这样做)。

当然,PEP 8的规定不是没有原因的。在这种情况下,有一个比个人喜好和统一性更好的理由:如果把所有的导入放在文件的顶部,别人就能很容易找到一个模块的依赖关系。如果导入分散在文件的各个地方,这就会变得麻烦多了。而且,现在几乎每次调用你的库时都有可能出现ImportError错误,这就很不幸了:通常的工作流程是先导入所有东西,如果能成功导入,就假设它能正常工作(这在设置虚拟环境时是一个有用的手动测试)。如果代码写得不太好,可能会在执行输入输出操作时,突然调用你的函数(没想到会出现ImportError),然后就会对错误感到惊讶,导致无法正确清理。

还有一点小开销,就是每次调用一个包含导入的函数时,会执行一些额外的指令。不过,这种开销对于大多数情况来说是相对较小的,而且它不会重复导入模块(无论是两次、三次还是多次)。当然,这样做也违反了DRY原则。

面对这个问题,我和其他人通常选择把导入放在文件的顶部,并用try: ... except ImportError:包裹起来。这样你可以给一个虚拟值,发出警告,记录一些信息,或者做其他在你情况下合理的事情。你甚至可以导入一个替代模块(例如,当支持某些没有特定模块的旧Python版本时)或者你自己提供的一个占位模块。

9

这样做不会明显影响性能,但会让你的代码变得杂乱。如果你决定添加一个新的导入,或者需要更改一个旧的导入,你就得到处修改,而不是只在一个地方改。

另外,你要确保这点有记录。有些用户可能会感到不满,因为库看起来导入得很正常,但在调用某个特定函数时却失败了。此外,虽然整体性能没有下降,但可能会出现性能“重组”,导致在意想不到的地方变慢。第一次调用一个导入了 numpy 的函数时,它需要进行导入,这会花费一些时间。用户可能会觉得这样不好,想要把所有慢的导入都提前完成。

你可以通过把所有导入放在顶部来轻松实现类似的效果:

try:
    import numpy
except ImportError:
    warnings.warn("Numpy not available, some functions may not work!")

之后尝试使用那些试图访问 numpy 的函数时,就会出现 NameError 错误。通过使用警告(或者只是打印/记录一条消息),你可以提前通知用户某些功能将无法使用,而不是让他们在后面突然遇到问题。

撰写回答