Python 导入语句是否应始终位于模块的顶部?
国家: 导入总是放在文件的顶部,就在任何模块注释和docstring之后,模块全局变量和常量之前 但是,如果我正在导入的类/方法/函数只在极少数情况下使用,那么在需要时进行导入肯定更有效 这不是: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
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
异常,并以合理的方式处理它们。模块导入相当快,但不是即时的。这意味着:
- 将导入放在模块的顶部是可以的,因为这是一个只需支付一次的小成本
- 将导入放在函数中会导致对该函数的调用花费更长的时间
我看到的执行延迟导入的最佳原因是:
- 可选的库支持。如果您的代码有多个使用不同库的路径,请不要在未安装可选库的情况下中断
- 在插件的
中,该插件可能已导入但未实际使用。例如Bazaar插件,它使用\uuuu init\uuuuuu.py
的延迟加载框架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]])