PEP08 国家:

进口总是在顶部文件,只是在之后的任何模块的评论和文档字符串之前,以及在模块globals和常数。

然而,如果这类方法/方法/功能,我进口只是用在少数情况下,当然,它是更高效的做进口时,这是必要的?

不是这样的:

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()
有帮助吗?

解决方案

模块进口是相当快,但不是即时的。这意味着:

  • 把进口的顶端模块,是好的,因为它是一个微不足道的成本,只支付一次。
  • 把进口内的功能会引起呼吁的功能需要更长的时间。

所以,如果你关心的效率,把进口。只有将它们移到一个功能,如果分析显示,将有助(你 有没有 配置文件,看看这里最好以提高性能对吗?)


最好的原因我见过执行懒惰的进口是:

  • 可选的图书馆的支持。如果你的代码有多个路径,使用不同的图书馆,也不打破,如果一个可选的图书馆尚未安装。
  • __init__.py 一个插件,这可能是进口的,但不是实际使用。实例都集市插件,其使用 bzrlib's懒载的框架。

其他提示

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

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

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

如其他地方所述,存在速度惩罚。我已经在我的申请中对此进行了测量,发现它对我的目的来说是微不足道的。

能够预先查看所有模块依赖关系而不诉诸搜索(例如grep)也很好。但是,我关心模块依赖性的原因通常是因为我正在安装,重构或移动包含多个文件的整个系统,而不仅仅是单个模块。在这种情况下,我将要执行全局搜索以确保我具有系统级依赖性。所以我没有找到全球进口来帮助我理解系统的实际情况。

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

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

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

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

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

当函数被调用为零或一次时,第一个变体确实比第二个更有效。然而,在第二次和随后的调用中,“导入每个呼叫”。方法实际上效率较低。请参阅此链接了解延迟加载技术,该技术结合了两者的优点通过执行“懒惰导入”来实现。

但是除了效率之外还有其他原因,为什么你们可能更喜欢一个而不是另一个。一种方法是让读取代码的人更清楚该模块具有的依赖关系。它们也具有非常不同的故障特征 - 如果没有“日期时间”,则第一个故障特征将在加载时失效。模块,而第二个模块在调用方法之前不会失败。

添加注意:在IronPython中,导入可能比CPython昂贵得多,因为代码基本上是在导入时编译的。

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

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

如果你必须在意外时加载重量级模块,使用 __ import __ 函数动态加载它们可能更有意义,并且确定来捕获 ImportError 例外,并以合理的方式处理它们。

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

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

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

例如:

do_something_with_x(x)

我可以用以下方法调试:

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

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

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

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

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

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

除了效率之外,如果导入语句是预先的,那么预先更容易看到模块依赖性。将它们隐藏在代码中会使得更容易找到某些依赖的模块。

我个人一般都遵循PEP,除了像单元测试这样的东西,我不想总是加载,因为我知道除测试代码外不会使用它们。

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

(在审核时:约翰米利金说。)

这就像许多其他优化一样 - 你牺牲了一些速度的可读性。正如约翰所提到的,如果你已经完成了分析功课,发现这是一个非常有用的变化,你需要额外的速度,然后去做。把所有其他进口产品都记下来可能会很好:

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

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

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

只需完成 Moe的回答和原始问题:

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

  1. 我们可以在模块底部移动 from imports
  2. 我们可以在实际需要导入的函数或方法中移动 from imports 中的一个(这并不总是可行,因为您可以从多个地方使用它)。
  3. 我们可以将两个从导入中的一个更改为导入,如下所示: import a
  4. 所以,总结一下。如果你没有处理循环依赖并做某种伎俩以避免它们,那么最好将所有导入放在顶部,因为这个问题的其他答案已经解释过了。并且,当做这个“技巧”时,请包括评论,总是欢迎! :)

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

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

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

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

我很惊讶没有看到实际成本数字为重复的负载检查已经张贴,虽然有许多很好的解释什么。

如果你进口在上面,你把所载的打击无论什么。那是相当小,但是通常在毫秒,没有毫微秒。

如果你进口内的功能(s),然后你只有采取打击用于装载 如果 一个这些职能是首先呼吁。正如许多人指出,如果这不会发生在所有的,您保存负载时间。但是,如果功能(s)获得叫了很多,你采取一个反复,虽然小得多打击(用于检验它 已经装载;不对实际上重新装载)。另一方面,如@aaronasterling指出的还保存一点,因为进口内部的功能允许的功能使用稍微快 当地变量 查找到确定的名字以后(http://stackoverflow.com/questions/477096/python-import-coding-style/4789963#4789963).

这里是结果的简单测试,用于进口一些事从内部的一个函数。该次报告(Python2.7.14上2.3GHz i7)如下所示(第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

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

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

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

我想提一下我的用例,与@John Millikin和@ V.K提到的用例非常相似。 :

可选进口

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

通过这种方式,我可以做“重启并且全部运行”。无需等待导入,或者在失败时必须恢复其余的单元格。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top