2

我正在开发一个 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

4 回答 4

9

没有明显的性能影响,但它会使您的代码混乱。如果您决定添加新的导入或必须更改旧的导入,则必须在各处进行更改,而不仅仅是在一个地方进行更改。

此外,您应该确保这已记录在案。如果库似乎正确导入但稍后在调用特定函数时失败,一些用户可能会感到恼火。此外,虽然没有整体性能受到影响,但可能会出现性能“重新洗牌”,从而导致意外地方的减速。第一次调用导入的函数时numpy,它必须进行导入,这需要时间。用户也可能会发现这不受欢迎,并希望预先完成所有缓慢的导入。

您可以通过 all-at-the-top 导入轻松获得类似的效果:

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

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

于 2013-02-12T20:19:54.607 回答
3

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

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

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

当遇到这个问题时,我和其他人无论如何都选择将导入放在文件的顶部,并用try: ... except ImportError:. 然后你可以分配一个虚拟值,发出警告,记录一些东西,或者做任何对你的情况有意义的事情。您甚至可以导入替换模块(例如,当支持没有某些模块的旧 Python 版本时)或您自己提供的存根模块。

于 2013-02-12T20:21:15.183 回答
1

并不真地。

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

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

于 2013-02-12T20:19:47.727 回答
0

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

于 2013-02-12T20:19:10.643 回答