Ada*_*ter 373 python optimization coding-style
PEP 08指出:
导入总是放在文件的顶部,就在任何模块注释和文档字符串之后,以及模块全局变量和常量之前.
但是,如果我导入的类/方法/功能仅在极少数情况下使用,那么在需要时进行导入肯定会更有效率吗?
不是这个:
class SomeClass(object):
def not_often_called(self)
from datetime import datetime
self.datetime = datetime.now()
Run Code Online (Sandbox Code Playgroud)
比这更有效率?
from datetime import datetime
class SomeClass(object):
def not_often_called(self)
self.datetime = datetime.now()
Run Code Online (Sandbox Code Playgroud)
Joh*_*kin 264
模块导入速度非常快,但不是即时的.这意味着:
因此,如果您关心效率,请将进口放在首位.只有在你的分析显示有帮助的情况下才会将它们移动到一个函数中(你确实想要了解哪里可以提高性能,对吧?)
我见过执行延迟导入的最佳原因是:
__init__.py插件中,可能导入但未实际使用.例子是Bazaar插件,它使用了bzrlib延迟加载框架.Moe*_*Moe 76
将import语句放在函数内可以防止循环依赖.例如,如果你有2个模块,X.py和Y.py,并且它们都需要相互导入,那么当你导入其中一个导致无限循环的模块时,这将导致循环依赖.如果你在其中一个模块中移动import语句,那么它将不会尝试导入其他模块,直到调用该函数,并且该模块已经被导入,因此没有无限循环.请阅读此处了解更多信息 - effbot.org/zone/import-confusion.htm
小智 58
我采用了将所有导入放在使用它们的函数中的做法,而不是放在模块的顶部.
我得到的好处是能够更可靠地重构.当我将一个函数从一个模块移动到另一个模块时,我知道该函数将继续使用它的所有遗留测试.如果我在模块的顶部有我的导入,当我移动一个函数时,我发现我最终花了很多时间让新模块的导入完成且最小化.重构IDE可能会使这无关紧要.
如其他地方所述,存在速度惩罚.我已经在我的申请中对此进行了测量,发现它对我的目的来说是微不足道的.
能够预先看到所有模块依赖关系而不诉诸搜索(例如grep)也很好.但是,我关心模块依赖性的原因通常是因为我正在安装,重构或移动包含多个文件的整个系统,而不仅仅是单个模块.在这种情况下,我将要执行全局搜索以确保我具有系统级依赖性.所以我没有找到全球进口来帮助我理解系统的实际情况.
我通常将导入sys放入if __name__=='__main__'检查内部,然后将参数(如sys.argv[1:])传递给main()函数.这允许我main在sys尚未导入的上下文中使用.
Con*_*lls 38
大多数情况下,这对于清晰和明智而言是有用的,但情况并非总是如此.以下是模块导入可能存在于其他地方的情况的几个示例.
首先,您可以使用具有以下形式的单元测试的模块:
if __name__ == '__main__':
import foo
aa = foo.xyz() # initiate something for the test
Run Code Online (Sandbox Code Playgroud)
其次,您可能需要在运行时有条件地导入一些不同的模块.
if [condition]:
import foo as plugin_api
else:
import bar as plugin_api
xx = plugin_api.Plugin()
[...]
Run Code Online (Sandbox Code Playgroud)
在其他情况下,您可能会将导入放入代码中的其他部分.
Pat*_*ick 18
ImportErrors通常更方便。__name__ == "__main__"监管下的进口似乎非常合理。Curt提出了一个很好的观点:第二个版本更清晰,并且会在加载时而不是以后出乎意料地失败.
通常我不担心加载模块的效率,因为它(a)非常快,(b)大多数只发生在启动时.
如果你必须在意外的时候加载重量级模块,那么使用该__import__函数动态加载它们可能更有意义,并且一定要捕获ImportError异常,并以合理的方式处理它们.
我不担心前面加载模块的效率太高.模块占用的内存不会很大(假设它足够模块化),启动成本可以忽略不计.
在大多数情况下,您希望将模块加载到源文件的顶部.对于读取代码的人来说,它可以更容易地告诉哪个函数或对象来自哪个模块.
在代码中的其他位置导入模块的一个很好的理由是它是否在调试语句中使用.
例如:
do_something_with_x(x)
Run Code Online (Sandbox Code Playgroud)
我可以调试这个:
from pprint import pprint
pprint(x)
do_something_with_x(x)
Run Code Online (Sandbox Code Playgroud)
当然,在代码中的其他位置导入模块的另一个原因是您需要动态导入它们.这是因为你几乎没有任何选择.
我不担心前面加载模块的效率太高.模块占用的内存不会很大(假设它足够模块化),启动成本可以忽略不计.
这是一个权衡,只有程序员才能决定.
案例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
Run Code Online (Sandbox Code Playgroud)
(回顾:John Millikin说的话.)
这就像许多其他优化一样 - 你牺牲了一些速度的可读性.正如约翰所提到的,如果你已经完成了你的剖析作业,并发现这是一个非常有用的变化,你需要额外的速度,那就去吧.把所有其他进口产品都记下来可能是件好事:
from foo import bar
from baz import qux
# Note: datetime is imported in SomeClass below
Run Code Online (Sandbox Code Playgroud)
模块初始化仅发生一次 - 第一次导入时。如果相关模块来自标准库,那么您也可能会从程序中的其他模块导入它。对于像 datetime 这样流行的模块来说,它也可能是许多其他标准库的依赖项。由于模块初始化已经发生,因此导入语句的成本非常低。此时它所做的就是将现有模块对象绑定到本地范围。
将这些信息与可读性参数结合起来,我想说最好在模块范围内使用 import 语句。
只是为了完成Moe 的回答和原来的问题:
当我们必须处理循环依赖时,我们可以做一些“技巧”。假设我们正在使用模块a.py和 ,b.py其中分别包含x()和 b y()。然后:
from imports我们可以移动模块底部的其中之一。from imports我们可以移动实际需要导入的函数或方法内部之一(这并不总是可能的,因为您可以从多个地方使用它)。from imports为如下所示的导入:import a所以,总结一下。如果您不处理循环依赖项并采取某种技巧来避免它们,那么最好将所有导入放在顶部,因为这个问题的其他答案中已经解释了原因。请在执行此“技巧”时发表评论,我们总是欢迎的!:)
我很惊讶没有看到已经发布的重复负载检查的实际成本数字,尽管对预期的内容有很多很好的解释。
如果在顶部导入,则无论如何都会承受负载。这非常小,但通常以毫秒为单位,而不是纳秒。
如果导入功能(S)之内,那么你只需要命中的加载,如果和当首次调用这些功能之一。正如许多人指出的那样,如果根本没有发生这种情况,则可以节省加载时间。但是,如果函数被多次调用,您会重复但较小的命中(用于检查它是否已加载;而不是实际重新加载)。另一方面,正如@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
Run Code Online (Sandbox Code Playgroud)
编码:
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
Run Code Online (Sandbox Code Playgroud)