使用内联导入是否可能产生负面影响?

时间:2013-02-12 20:16:19

标签: python

我正在开发一个Python程序,即使某些功能所需的某些库缺失,也需要能够运行。 (编辑:我写了一些代码来实现最好的建议解决方案,它是here,带有doctest here。)

我通过将这些库的import语句内联到使用它们的函数而不是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

我的机器需要大约180毫秒,重复100,000次,但

def f():
  import numpy
  return numpy

需要大约870毫秒。

非常粗略的说法是,这需要多达四个微不足道的函数调用 - 在大多数情况下显而易见但不显着。尽管如此,如果不花费你的话,最好避免这样做。

在实验中,我还意识到了内联导入的另一个缺点 - 当调用该函数时,这些导入会在不可预测的时间内消失。在我的具有实时元素的应用程序中,这是不可接受的。

4 个答案:

答案 0 :(得分:9)

没有明显的性能损失,但它会让您的代码变得混乱。如果您决定添加新导入或必须更改旧导入,则必须在任何地方而不是仅在一个地方进行更改。

此外,您应该确定这是记录在案的。如果库看起来导入正确但有些用户可能会感到烦躁,但是在调用特定函数后很快就会失败。此外,虽然没有全面的性能影响,但可能会出现性能“重新洗牌”,导致意想不到的地方放缓。第一次调用导入numpy的函数时,必须进行导入,这需要时间。用户也可能发现这种情况不受欢迎,并且希望所有缓慢的导入都可以预先完成。

您可以轻松获得与顶级导入类似的效果:

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

稍后尝试使用尝试访问numpy的函数的尝试现在将失败并出现NameError。通过使用警告(或只是打印/记录的消息),您还提前通知某些事情不起作用,而不是稍后突然失败。

答案 1 :(得分:3)

你这样做并没有关注PEP 8。在标准库导入的情况下,你没有充分的理由这样做,这是一个双重的错误,足以让一些人回避你的代码(或至少礼貌地唠叨你不应该这样做)。

当然,PEP 8并没有说完全没有理由。在这种情况下,有一个比个人偏好和一致性更好的理由:如果将所有导入放在顶部,可以很容易地找到模块的依赖关系。如果导入遍布整个文件,这就变得更麻烦了。此外,现在几乎每次调用你的库都会引发ImportError,这是相当不幸的:通常的工作流程是导入所有内容,如果可以导入,则认为它可以工作(这在设置时是一个有用的手动测试一个virtualenv)。编写得不好的代码可能会开始执行I / O之类的操作,在两者之间调用函数(不要期望ImportError),然后对错误感到惊讶并且无法正确清理。

还有一个轻微的开销,因为每次调用包含导入的函数时,都会执行一些额外的指令。但是,对于大多数用途而言,这种开销相当小,并且它不会导入模块两次(或三次,或无数次)。当然,它也违反了DRY。

当遇到这个问题时,我和其他人选择将导入放在文件的顶部,用try: ... except ImportError:包围。然后,您可以分配虚拟值,发出警告,记录某些内容,或者执行其他任何有意义的操作。您甚至可以导入替换模块(例如,在支持没有某些模块的旧Python版本时)或您自己提供的存根模块。

答案 2 :(得分:1)

不是。

导入只会发生一次,但可能会发生在意外的时间(对于用户),即第一次调用导入该函数的函数。

此外,这是一个可读性的问题 - 如果按照惯例在顶部进行导入,代码的每个读者都会立即知道它的依赖性。当导入发生在模块的第284行时,这种清晰度可能会丢失......

答案 3 :(得分:0)

不,这样做不应该有任何缺点或隐藏成本。模块被缓存并且只执行一次,即使您多次导入它们也是如此。然后导入只是(重新)设置模块的本地引用。