Python 导入语句是否应始终位于模块的顶部?

Python 导入语句是否应始终位于模块的顶部?,python,optimization,pep8,Python,Optimization,Pep8,国家: 导入总是放在文件的顶部,就在任何模块注释和docstring之后,模块全局变量和常量之前 但是,如果我正在导入的类/方法/函数只在极少数情况下使用,那么在需要时进行导入肯定更有效 这不是: class SomeClass(object): def not_often_called(self) from datetime import datetime self.datetime = datetime.now() 比这更有效 from datet

国家:

导入总是放在文件的顶部,就在任何模块注释和docstring之后,模块全局变量和常量之前

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

这不是:

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

当函数被调用零次或一次时,第一个变量确实比第二个更有效。然而,对于第二次和后续调用,“导入每个调用”方法实际上效率较低。请参阅,了解通过执行“惰性导入”将两种方法的优点结合起来的惰性加载技术

但是,除了效率之外,还有其他原因让你更喜欢其中一个。一种方法是让阅读代码的人更清楚地了解该模块的依赖关系。它们也有非常不同的故障特征——如果没有“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()
[...]

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

这是一种折衷,只有程序员才能做出决定

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

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

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


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

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

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


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

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

  • 将导入放在模块的顶部是可以的,因为这是一个只需支付一次的小成本
  • 将导入放在函数中会导致对该函数的调用花费更长的时间
因此,如果你关心效率,就把进口放在首位。只有在您的配置文件显示有帮助的情况下,才将它们移动到函数中(您做了配置文件,以查看在哪里可以最好地提高性能,对不对??)


我看到的执行延迟导入的最佳原因是:

  • 可选的库支持。如果您的代码有多个使用不同库的路径,请不要在未安装可选库的情况下中断
  • 在插件的
    \uuuu init\uuuuuu.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
      

      (评论:what said。)

      这与许多其他优化一样——为了提高速度,您牺牲了一些可读性。正如John提到的,如果您已经完成了评测作业,并且发现这是一个非常有用的更改您需要额外的速度,那么就去做吧。在所有其他进口商品上贴一张便条可能会更好:

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

      模块初始化仅在第一次导入时发生一次。如果所讨论的模块来自标准库,那么您可能也会从程序中的其他模块导入它。
       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
      
      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()
      
      from test import add, X, Y, Z
      
      def callme():
        for i in range(100000000):
          add(i)
          X+Y+Z
      
      callme()
      
      /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
      
      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]])