Import 语句应该总是位于模块的顶部吗?

PEP 8 规定:

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

但是,如果我导入的类/方法/函数只在极少数情况下使用,那么在需要时进行导入肯定更有效率吧?

这不就是:

class SomeClass(object):


def not_often_called(self)
from datetime import datetime
self.datetime = datetime.now()

更有效率吗?

from datetime import datetime


class SomeClass(object):


def not_often_called(self)
self.datetime = datetime.now()
163964 次浏览

当函数被调用0次或1次时,第一种变体确实比第二种更有效。然而,对于第二次和后续调用,“导入每个调用”方法实际上效率较低。请参阅这个链接以获得延迟加载技术,该技术通过执行“延迟导入”将两种方法的优点结合起来。

但除了效率之外,还有其他原因可以解释为什么你会更喜欢其中一种。一种方法是让阅读代码的人更清楚地了解这个模块所具有的依赖关系。它们也有非常不同的失败特征——如果没有“datetime”模块,第一个将在加载时失败,而第二个直到方法被调用才会失败。

添加备注:在IronPython中,导入可能比在CPython中要昂贵一些,因为代码基本上是在导入时被编译的。

我不太担心预先加载模块的效率。模块占用的内存不会很大(假设它足够模块化),启动成本可以忽略不计。

在大多数情况下,您希望在源文件的顶部加载模块。对于阅读代码的人来说,它可以更容易地区分哪个函数或对象来自哪个模块。

在代码的其他地方导入模块的一个很好的理由是,如果它在调试语句中使用。

例如:

do_something_with_x(x)

我可以用:

from pprint import pprint
pprint(x)
do_something_with_x(x)

当然,在代码的其他地方导入模块的另一个原因是,如果您需要动态导入它们。这是因为你几乎没有任何选择。

我不太担心预先加载模块的效率。模块占用的内存不会很大(假设它足够模块化),启动成本可以忽略不计。

大多数情况下,这对于清晰和明智的做法是有用的,但并不总是如此。下面是模块导入可能存在于其他地方的两个例子。

首先,你可以有一个这样的单元测试模块:

if __name__ == '__main__':
import foo
aa = foo.xyz()         # initiate something for the test

其次,您可能需要在运行时有条件地导入一些不同的模块。

if [condition]:
import foo as plugin_api
else:
import bar as plugin_api
xx = plugin_api.Plugin()
[...]

在其他情况下,您可能会在代码的其他部分导入。

这是一个只有程序员才能决定的权衡。

Case 1在需要时才导入datetime模块(并进行任何可能需要的初始化),从而节省了一些内存和启动时间。请注意,“仅在被调用时”导入也意味着“每次被调用时”导入,因此第一次调用之后的每个调用仍然会产生执行导入的额外开销。

情况2通过提前导入datetime来节省一些执行时间和延迟,这样当not_经常调用()调用时,它会更快地返回,而且也不会在每次调用时都产生导入的开销。

除了效率,如果import语句是…前面。将它们隐藏在代码中会使查找某个组件所依赖的模块变得更加困难。

就我个人而言,我通常遵循PEP,除了像单元测试这样的事情,我不希望总是加载,因为我知道它们除了测试代码外不会被使用。

Curt提出了一个很好的观点:第二个版本更清晰,并且会在加载时失败,而不是在加载后失败,而且出乎意料。

通常我不担心加载模块的效率,因为它(a)非常快,(b)大多数只发生在启动时。

如果你不得不在意想不到的时候加载重量级模块,使用__import__函数动态加载它们可能更有意义,并使用确定来捕获ImportError异常,并以合理的方式处理它们。

模块导入非常快,但不是即时的。这意味着:

  • 将导入放在模块的顶部是可以的,因为这是一个微不足道的成本,只需要支付一次。
  • 将导入放在函数中会导致对该函数的调用花费更长的时间。

所以如果你关心效率,把进口放在最上面。只有在你的概要分析显示有帮助的情况下才将它们移动到函数中(你做了概要文件查看哪里最能提高性能,对吗??)


我所见过的执行惰性导入的最佳理由是:

  • 可选的库支持。如果您的代码有多个使用不同库的路径,如果没有安装可选库,请不要中断。
  • 在插件的__init__.py中,它可能被导入,但实际上没有使用。例如Bazaar插件,它们使用bzrlib的惰性加载框架。

下面是一个示例,其中所有导入都位于最顶部(这是我唯一一次需要这样做)。我希望能够在Un*x和Windows上终止子进程。

import os
# ...
try:
kill = os.kill  # will raise AttributeError on Windows
from signal import SIGTERM
def terminate(process):
kill(process.pid, SIGTERM)
except (AttributeError, ImportError):
try:
from win32api import TerminateProcess  # use win32api if available
def terminate(process):
TerminateProcess(int(process._handle), -1)
except ImportError:
def terminate(process):
raise NotImplementedError  # define a dummy function

(回顾:约翰Millikin说了什么。)

这就像许多其他优化一样——你牺牲了一些可读性来换取速度。正如John提到的,如果你已经做了你的分析作业,并发现这是一个非常有用的变化而且,你需要额外的速度,然后去做它。最好把所有其他的导入都放在一起:

from foo import bar
from baz import qux
# Note: datetime is imported in SomeClass below

模块初始化只发生一次——在第一次导入时。如果有问题的模块来自标准库,那么您可能也会从程序中的其他模块导入它。对于像datetime这样流行的模块,它也可能依赖于大量其他标准库。import语句的开销非常小,因为模块初始化已经完成了。此时它所做的只是将现有的模块对象绑定到本地作用域。

将这些信息与可读性参数结合起来,我会说import语句最好在模块范围内。

我采用了将所有导入放在使用它们的函数中,而不是放在模块的顶部的做法。

这样做的好处是能够更可靠地进行重构。当我将一个函数从一个模块移动到另一个模块时,我知道该函数将继续工作,并且保留所有遗留的测试。如果我将导入放在模块的顶部,当我移动一个函数时,我发现我最终要花费大量时间来完成新模块的导入并使其最小化。重构IDE可能会让这一点变得无关紧要。

正如在其他地方提到的那样,有一个速度惩罚。我在我的应用程序中测量了这一点,发现它对我的目的来说是微不足道的。

不需要搜索(例如grep)就能看到所有模块依赖关系也是很好的。然而,我关心模块依赖关系的原因通常是因为我正在安装、重构或移动由多个文件组成的整个系统,而不仅仅是单个模块。在这种情况下,我无论如何都要执行全局搜索,以确保具有系统级依赖关系。因此,我还没有找到全局导入来帮助我在实践中理解一个系统。

我通常将sys的导入放在if __name__=='__main__'检查中,然后将参数(如sys.argv[1:])传递给main()函数。这允许我在尚未导入sys的上下文中使用main

将import语句放在函数内部可以防止循环依赖。 例如,如果您有两个模块,X.py和Y.py,它们都需要相互导入,当您导入其中一个模块时,这会导致循环依赖,从而导致无限循环。如果你在其中一个模块中移动import语句,那么它不会尝试导入另一个模块,直到函数被调用,而那个模块已经被导入了,所以没有无限循环。阅读这里了解更多- effbot.org/zone/import-confusion.htm

完成Moe的回答和原来的问题:

假设我们正在处理模块a.pyb.py,它们分别包含x()和b y()。然后:

  1. 我们可以移动模块底部的from imports之一。
  2. 我们可以在实际需要导入的函数或方法中移动其中一个from imports(这并不总是可行的,因为你可能在多个地方使用它)。
  3. 我们可以将两个from imports中的一个更改为类似import a的导入

总结一下。如果您没有处理循环依赖关系,也没有使用某种技巧来避免它们,那么最好将所有导入放在顶部,因为原因已经在这个问题的其他答案中解释过了。请在做这些“技巧”时附上评论,这总是受欢迎的!:)

除了已经给出的优秀答案之外,值得注意的是导入的位置不仅仅是风格的问题。有时,模块具有需要首先导入或初始化的隐式依赖项,而顶层导入可能会导致违反所需的执行顺序。

这个问题经常出现在Apache Spark的Python API中,在导入任何pyspark包或模块之前,你需要初始化SparkContext。最好将pyspark导入放在保证SparkContext可用的范围内。

我不想提供完整的答案,因为其他人已经做得很好了。我只想提到一个用例,我发现在函数中导入模块特别有用。我的应用程序使用python包和模块存储在特定的位置作为插件。在应用程序启动期间,应用程序遍历位置中的所有模块并导入它们,然后查看模块内部,如果它找到插件的一些挂载点(在我的例子中,它是具有唯一ID的某个基类的子类),它就会注册它们。插件的数量很大(现在有几十个,将来可能有几百个),而且每个插件都很少被使用。在我的插件模块的顶部导入第三方库在应用程序启动过程中是一种惩罚。特别是一些第三方库的导入是繁重的(例如,导入plotly甚至试图连接到互联网并下载一些东西,这增加了大约一秒钟的启动时间)。通过优化插件中的导入(只在使用它们的函数中调用它们),我成功地将启动时间从10秒缩短到2秒左右。这对我的用户来说是一个很大的不同。

所以我的答案是否定的,不要总是把导入放在模块的顶部。

有趣的是,到目前为止,没有一个回答提到并行处理,当序列化的函数代码被推到其他核心时,可能需要将导入放在函数中,例如在ipyparallel的情况下。

我很惊讶没有看到重复负载检查的实际成本数字,尽管有很多很好的解释。

如果你在顶部导入,不管发生什么,你都要加载命中。这非常小,但通常是毫秒级,而不是纳秒级。

如果在函数中导入,则只会加载如果,其中一个函数会首先被调用。正如许多人指出的那样,如果完全不发生这种情况,就可以节省加载时间。但是,如果函数被多次调用,则会重复执行一个小得多的命中(用于检查是否已加载;而不是实际重新加载)。另一方面,正如@aaronasterling指出的那样,你也节省了一点,因为在函数内导入可以让函数稍后使用稍微快一点的局部变量查找来识别名称(http://stackoverflow.com/questions/477096/python-import-coding-style/4789963#4789963)。

下面是一个简单测试的结果,该测试从函数内部导入了一些内容。报告的时间(在2.3 GHz Intel Core i7上的Python 2.7.14中)如下所示(第2个调用比后面的调用多似乎是一致的,尽管我不知道为什么)。

 0 foo:   14429.0924 µs
1 foo:      63.8962 µs
2 foo:      10.0136 µs
3 foo:       7.1526 µs
4 foo:       7.8678 µs
0 bar:       9.0599 µs
1 bar:       6.9141 µs
2 bar:       7.1526 µs
3 bar:       7.8678 µs
4 bar:       7.1526 µs

代码:

from __future__ import print_function
from time import time


def foo():
import collections
import re
import string
import math
import subprocess
return


def bar():
import collections
import re
import string
import math
import subprocess
return


t0 = time()
for i in xrange(5):
foo()
t1 = time()
print("    %2d foo: %12.4f \xC2\xB5s" % (i, (t1-t0)*1E6))
t0 = t1
for i in xrange(5):
bar()
t1 = time()
print("    %2d bar: %12.4f \xC2\xB5s" % (i, (t1-t0)*1E6))
t0 = t1

我想提一下我的一个用例,与@John Millikin和@ v.k.提到的用例非常相似:

可选的进口

我使用Jupyter Notebook进行数据分析,我使用相同的IPython Notebook作为所有分析的模板。在某些情况下,我需要导入Tensorflow来做一些快速的模型运行,但有时我工作的地方,Tensorflow没有设置/导入很慢。在这些情况下,我将依赖于tensorflow的操作封装在一个helper函数中,在该函数中导入tensorflow,并将其绑定到一个按钮。

这样,我可以“重新启动并运行全部”;无需等待导入,也无需在导入失败时恢复其余单元格。

在函数中导入变量/局部作用域可以提高性能。这取决于函数中导入对象的使用情况。如果你多次循环并访问一个模块全局对象,将它导入为本地会有帮助。

test.py

X=10
Y=11
Z=12
def add(i):
i = i + 10

runlocal.py

from test import add, X, Y, Z


def callme():
x=X
y=Y
z=Z
ladd=add
for i  in range(100000000):
ladd(i)
x+y+z


callme()

run.py

from test import add, X, Y, Z


def callme():
for i in range(100000000):
add(i)
X+Y+Z


callme()

在Linux上的时间显示了一个小的增益

/usr/bin/time -f "\t%E real,\t%U user,\t%S sys" python run.py
0:17.80 real,   17.77 user, 0.01 sys
/tmp/test$ /usr/bin/time -f "\t%E real,\t%U user,\t%S sys" python runlocal.py
0:14.23 real,   14.22 user, 0.01 sys

真实的是挂钟。用户是程序中的时间。Sys是系统调用的时间。

https://docs.python.org/3.5/reference/executionmodel.html#resolution-of-names

这是一个引人入胜的讨论。和许多人一样,我从未考虑过这个话题。我不得不在函数中导入,因为我想在我的一个库中使用Django ORM。在导入我的模型类之前,我不得不调用django.setup(),因为这是在文件的顶部,它被拖到完全非django库代码中,因为IoC注入器结构。

我稍微改了一下,最后把django.setup()放在了单例构造函数中,并把相关的导入放在了每个类方法的顶部。现在这运行得很好,但让我感到不安,因为导入不在顶部,而且我开始担心导入的额外时间。然后我来到这里,怀着极大的兴趣阅读了大家对此的看法。

我有很长的c++背景,现在使用Python/Cython。我对此的看法是,为什么不把导入放在函数中,除非它会导致一个概要瓶颈。这就像在你需要变量之前为它们声明空间一样。问题是我有数千行代码,所有的导入都在顶部!所以我想从现在开始,当我有时间的时候,我会在这里和那里改变奇怪的文件。

可读性

除了启动性能外,本地化import语句还有一个可读性方面的问题。例如,在我目前的第一个python项目中使用python行号1283到1296:

listdata.append(['tk font version', font_version])
listdata.append(['Gtk version', str(Gtk.get_major_version())+"."+
str(Gtk.get_minor_version())+"."+
str(Gtk.get_micro_version())])


import xml.etree.ElementTree as ET


xmltree = ET.parse('/usr/share/gnome/gnome-version.xml')
xmlroot = xmltree.getroot()
result = []
for child in xmlroot:
result.append(child.text)
listdata.append(['Gnome version', result[0]+"."+result[1]+"."+
result[2]+" "+result[3]])

如果import语句在文件的顶部,我将不得不向上滚动很长一段路,或按首页,以找出ET是什么。然后我将不得不返回到第1283行继续阅读代码。

事实上,即使import语句像许多人那样位于函数(或类)的顶部,也需要向上和向下分页。

很少会显示Gnome版本号,因此文件顶部的import会引入不必要的启动延迟。

虽然鼓舞士气的鼓励在模块顶部导入,但在其他级别导入并不是错误。这表明进口应该在顶部,但也有例外。

在使用模块时加载模块是一种微优化。导入缓慢的代码可以在以后进行优化,如果这会产生相当大的差异的话。

不过,您可以在尽可能靠近顶部的位置引入标志,以便有条件地导入,允许用户使用配置导入所需的模块,同时仍然立即导入所有内容。

尽快导入意味着如果任何导入(或导入的导入)缺失或有语法错误,程序将失败。如果所有导入都发生在所有模块的顶部,则python分两步工作。编译。运行。

内置模块可以在任何导入它们的地方工作,因为它们设计得很好。您编写的模块应该是相同的。将导入移动到顶部或它们的第一次使用位置有助于确保没有副作用,并且代码正在注入依赖项。

无论您是否将导入放在顶部,当导入放在顶部时,代码都应该仍然可以工作。所以从立即导入开始,然后根据需要进行优化。

下面是这个问题答案的更新总结 < a href = " https://stackoverflow.com/questions/1188640 " >和< / > < a href = " https://stackoverflow.com/questions/1024049 " >和< / > 问题。< / p >
  • PEP 8
  • . 它通常更方便得到 < a href = " https://stackoverflow.com/a/128522/38281 " > ImportErrors < / > 当你第一次运行程序时 而不是当程序第一次调用函数时
  • 将导入放入函数作用域 可以帮助避免圆形的进口问题。
  • 将导入放入函数作用域 帮助保持干净模块命名空间, 这样它就不会出现在制表符补全建议中 <李> 启动时间: 函数中的导入直到(如果)该函数被调用才会运行。 对于重量级的库可能会很重要 尽管import语句在随后的运行中非常快, 它们仍然会招致速度惩罚
  • ?
  • __name__ == "__main__"保护看起来很合理下导入。
  • < a href = " https://stackoverflow.com/a/128859/38281 " >重构 如果导入位于函数中,可能会更容易 它们的使用地点(便于将其移动到另一个模块)。 也可以认为这对可读性有好处。 然而,大多数人会持相反意见,即
  • 在顶部导入增强可读性, 因为你可以一眼看到你所有的依赖关系
  • 目前还不清楚动态有条件的导入是否更倾向于一种样式。