我正在开发一个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毫秒。
非常粗略的说法是,这需要多达四个微不足道的函数调用 - 在大多数情况下显而易见但不显着。尽管如此,如果不花费你的话,最好避免这样做。
在实验中,我还意识到了内联导入的另一个缺点 - 当调用该函数时,这些导入会在不可预测的时间内消失。在我的具有实时元素的应用程序中,这是不可接受的。
答案 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)
不,这样做不应该有任何缺点或隐藏成本。模块被缓存并且只执行一次,即使您多次导入它们也是如此。然后导入只是(重新)设置模块的本地引用。