482

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()
4

22 回答 22

345

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

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

因此,如果您关心效率,请将进口放在首位。仅当您的分析显示有帮助时才将它们移动到一个函数中(您进行了分析以查看在哪里可以最好地提高性能,对吗??)


我见过执行惰性导入的最佳原因是:

  • 可选的库支持。如果您的代码有多个使用不同库的路径,如果未安装可选库,请不要中断。
  • __init__.py插件中,它可能被导入但未实际使用。示例是 Bazaar 插件,它使用bzrlib' 的延迟加载框架。
于 2008-09-24T17:38:00.760 回答
93

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

于 2008-09-24T20:36:46.977 回答
73

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

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

如其他地方所述,存在速度惩罚。我在我的应用程序中测量了这一点,发现它对我的目的来说微不足道。

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

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

于 2008-09-24T18:16:13.397 回答
43

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

首先,您可以拥有一个带有以下形式的单元测试的模块:

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()
[...]

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

于 2008-09-24T17:31:08.780 回答
15

当函数被调用零次或一次时,第一个变体确实比第二个更有效。然而,对于第二次和随后的调用,“导入每个调用”方法实际上效率较低。有关延迟加载技术,请参阅此链接,该技术通过“延迟导入”结合了两种方法的优点。

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

补充说明:在 IronPython 中,导入的成本可能比在 CPython 中高很多,因为代码基本上是在导入时编译的。

于 2008-09-24T17:30:04.570 回答
9

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

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

__import__如果您必须在意外的时间加载重量级模块,使用函数动态加载它们可能更有意义,并确保捕获ImportError异常并以合理的方式处理它们。

于 2008-09-24T17:32:50.647 回答
8

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

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

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

例如:

do_something_with_x(x)

我可以使用以下方法进行调试:

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

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

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

于 2008-09-24T17:30:34.320 回答
6

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

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

案例 2 通过预先导入 datetime 来节省一些执行时间和延迟,这样 not_often_called()在调用时会更快地返回,并且不会在每次调用时产生导入开销。

除了效率之外,如果 import 语句是……预先看到的,就更容易预先看到模块依赖关系。将它们隐藏在代码中会使更难找到某些模块所依赖的模块。

就个人而言,我通常遵循 PEP,除了单元测试之类的东西,我不想总是加载,因为我知道除了测试代码之外它们不会被使用。

于 2008-09-24T17:31:17.093 回答
6

这是一个示例,其中所有导入都位于最顶部(这是我唯一需要这样做的时间)。我希望能够终止 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

(审查:约翰·米利金所说的。)

于 2008-09-24T17:48:02.827 回答
6

这就像许多其他优化一样——你牺牲了一些可读性来换取速度。正如 John 所提到的,如果您已经完成了分析作业并发现这是一个非常有用的更改,并且您需要额外的速度,那么就去做吧。与所有其他导入一起记下可能会很好:

from foo import bar
from baz import qux
# Note: datetime is imported in SomeClass below
于 2008-09-24T17:49:54.477 回答
5

模块初始化只发生一次 - 在第一次导入时。如果有问题的模块来自标准库,那么您也可能会从程序中的其他模块导入它。对于像 datetime 这样流行的模块,它也可能是许多其他标准库的依赖项。由于模块初始化已经发生,因此导入语句的成本非常低。此时它所做的只是将现有的模块对象绑定到本地范围。

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

于 2008-09-24T17:52:54.843 回答
5

我很惊讶没有看到已经发布的重复负载检查的实际成本数字,尽管对预期结果有很多很好的解释。

如果你在顶部导入,无论如何你都会承受负载。这非常小,但通常以毫秒为单位,而不是纳秒。

如果您在函数中导入,那么只有在首次调用其中一个函数时才会加载。正如许多人指出的那样,如果根本没有发生这种情况,您可以节省加载时间。但是如果函数被调用了很多次,你会重复一次,但要小得多(用于检查它是否已加载;而不是实际重新加载)。另一方面,正如@aaronasterling 指出的那样,您还可以节省一点,因为在函数中导入可以让函数使用稍快的局部变量查找来稍后识别名称(http://stackoverflow.com/questions/477096/python-导入编码样式/4789963#4789963)。

这是从函数内部导入一些东西的简单测试的结果。报告的时间(在 2.3 GHz 英特尔酷睿 i7 上的 Python 2.7.14 中)如下所示(第二次调用比后来的调用多似乎是一致的,尽管我不知道为什么)。

 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
于 2018-05-10T22:23:49.773 回答
4

只是为了完成Moe 的回答和原始问题:

当我们必须处理循环依赖时,我们可以做一些“技巧”。假设我们正在使用分别包含a.py和b的模块。然后:b.pyx()y()

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

所以,总结一下。如果您没有处理循环依赖并采取某种技巧来避免它们,那么最好将所有导入放在顶部,因为该问题的其他答案中已经解释了原因。并且请在执行此“技巧”时包含评论,它总是受欢迎的!:)

于 2013-07-01T14:41:01.067 回答
4

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

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

于 2016-04-04T14:56:03.900 回答
3

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

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

于 2016-11-29T09:36:53.970 回答
3

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

于 2018-04-09T23:50:26.103 回答
3

这是对此 相关 问题的答案的更新摘要。

  • PEP 8 建议 将导入放在顶部。
  • 当你第一次运行你的程序而不是当你的程序第一次调用你的函数时 获取s通常更方便。ImportError
  • 顶部的导入增强了可读性,因为您可以一目了然地看到所有依赖项。
  • 如果导入位于 使用它们的函数中(便于将其移动到另一个模块),重构可能会更容易。也可以说这有利于可读性
  • 将导入放在函数范围内可以帮助避免循环导入的问题。
  • 将导入放在函数范围内有助于保持模块命名空间干净,这样它就不会出现在选项卡完成建议中。
  • 启动时间:函数中的导入在(如果)调用该函数之前不会运行。使用重量级库可能会变得很重要。
  • 即使 import 语句在随后的运行中速度非常快,但 如果函数很简单但经常使用,它们仍然会导致速度损失,这可能会很严重。
  • 进口__name__ == "__main__"看守似乎很合理
  • 似乎还不清楚动态条件导入是否更喜欢一种风格而不是另一种风格。
于 2021-11-11T12:37:02.123 回答
2

可读性

除了启动性能之外,还有一个可读性参数用于本地化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语句在文件的顶部,我将不得不向上滚动很长一段路,或者按Home,找出是什么ET。然后我必须导航回第 1283 行继续阅读代码。

实际上,即使该import语句位于函数(或类)的顶部,正如许多人所放置的那样,也需要向上和向后翻页。

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

于 2020-01-19T18:11:30.240 回答
1

通过在函数内部导入变量/局部范围可以提高性能。这取决于函数内部导入的东西的用法。如果您要循环多次并访问模块全局对象,则将其作为本地导入会有所帮助。

测试.py

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

运行本地文件

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()

运行.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

于 2018-11-06T09:15:53.293 回答
1

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

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

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

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

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

无论您是否将导入放在顶部,当导入位于顶部时,您的代码应该仍然可以工作。因此,首先立即导入,然后根据需要进行优化。

于 2021-09-14T02:42:13.147 回答
0

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

可选导入

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

这样,我可以执行“重新启动并运行所有”操作,而无需等待导入,也不必在失败时恢复其余单元。

于 2018-07-05T19:37:34.383 回答
0

这是一个有趣的讨论。像许多其他人一样,我什至从未考虑过这个话题。由于想在我的一个库中使用 Django ORM,我不得不在函数中进行导入。我不得不django.setup()在导入我的模型类之前调用​​,因为它位于文件的顶部,由于 IoC 注入器构造,它被拖到完全非 Django 库代码中。

我稍微修改了一下,最终将django.setup()单例构造函数和相关导入放在每个类方法的顶部。现在这工作正常,但让我感到不安,因为进口不在顶部,而且我开始担心进口的额外时间打击。然后我来到这里,怀着极大的兴趣阅读了每个人对此的看法。

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

于 2019-09-16T08:33:02.053 回答