PEP 08指出:
导入总是放在文件的顶部,就在任何模块注释和文档字符串之后,以及模块全局变量和常量之前.
但是,如果我导入的类/方法/功能仅在极少数情况下使用,那么在需要时进行导入肯定会更有效率吗?
不是这个:
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()
John Milliki.. 264
模块导入速度非常快,但不是即时的.这意味着:
将导入放在模块的顶部是很好的,因为这是一个微不足道的成本,只需支付一次.
将导入放在函数中将导致对该函数的调用花费更长时间.
因此,如果您关心效率,请将进口放在首位.只有在你的分析显示有帮助的情况下才会将它们移动到一个函数中(你确实想要了解哪里可以提高性能,对吧?)
我见过执行延迟导入的最佳原因是:
可选的库支持.如果您的代码有多个使用不同库的路径,请不要在未安装可选库的情况下中断.
在__init__.py
插件中,可能导入但未实际使用.例子是Bazaar插件,它使用了bzrlib
延迟加载框架.
>将导入放在函数中将导致对该函数的调用花费更长时间.实际上,我认为这笔费用只支付一次.我已经读过Python缓存导入的模块,因此再次导入它只需要很少的成本. (37认同)
@halfhourhacks Python不会重新导入模块,但它仍然需要执行一些指令,只是为了查看模块是否存在/在sys.modules /等中. (24认同)
-1.将导入放在一个函数中并不一定会导致它需要更长的时间.关于另一个问题,请参阅我的[回答](http://stackoverflow.com/questions/477096/python-import-coding-style/4789963#4789963). (23认同)
约翰,这是一个完全理论上的问题,所以我没有任何代码可以分析.在过去,我一直关注PEP,但今天我写了一些代码,让我想知道这是不是正确的事情.谢谢你的帮助. (16认同)
一个用例是避免循环导入(通常不合理,但有时候是合适的).有时,模块m1中的类A调用模块m2中的类B上的方法,该方法构造类A的另一个实例.如果构造类A实例的类B中的方法仅在执行构造实例的函数时才执行导入,避免循环导入. (4认同)
Moe.. 76
将import语句放在函数内可以防止循环依赖.例如,如果你有2个模块,X.py和Y.py,并且它们都需要相互导入,那么当你导入其中一个导致无限循环的模块时,这将导致循环依赖.如果你在其中一个模块中移动import语句,那么它将不会尝试导入其他模块,直到调用该函数,并且该模块已经被导入,因此没有无限循环.请阅读此处了解更多信息 - effbot.org/zone/import-confusion.htm
模块导入速度非常快,但不是即时的.这意味着:
将导入放在模块的顶部是很好的,因为这是一个微不足道的成本,只需支付一次.
将导入放在函数中将导致对该函数的调用花费更长时间.
因此,如果您关心效率,请将进口放在首位.只有在你的分析显示有帮助的情况下才会将它们移动到一个函数中(你确实想要了解哪里可以提高性能,对吧?)
我见过执行延迟导入的最佳原因是:
可选的库支持.如果您的代码有多个使用不同库的路径,请不要在未安装可选库的情况下中断.
在__init__.py
插件中,可能导入但未实际使用.例子是Bazaar插件,它使用了bzrlib
延迟加载框架.
将import语句放在函数内可以防止循环依赖.例如,如果你有2个模块,X.py和Y.py,并且它们都需要相互导入,那么当你导入其中一个导致无限循环的模块时,这将导致循环依赖.如果你在其中一个模块中移动import语句,那么它将不会尝试导入其他模块,直到调用该函数,并且该模块已经被导入,因此没有无限循环.请阅读此处了解更多信息 - effbot.org/zone/import-confusion.htm
我采用了将所有导入放在使用它们的函数中的做法,而不是放在模块的顶部.
我得到的好处是能够更可靠地重构.当我将一个函数从一个模块移动到另一个模块时,我知道该函数将继续使用它的所有遗留测试.如果我在模块的顶部有我的导入,当我移动一个函数时,我发现我最终花了很多时间让新模块的导入完成且最小化.重构IDE可能会使这无关紧要.
如其他地方所述,存在速度惩罚.我已经在我的申请中对此进行了测量,发现它对我的目的来说是微不足道的.
能够预先看到所有模块依赖关系而不诉诸搜索(例如grep)也很好.但是,我关心模块依赖性的原因通常是因为我正在安装,重构或移动包含多个文件的整个系统,而不仅仅是单个模块.在这种情况下,我将要执行全局搜索以确保我具有系统级依赖性.所以我没有找到全球进口来帮助我理解系统的实际情况.
我通常将导入sys
放入if __name__=='__main__'
检查内部,然后将参数(如sys.argv[1:]
)传递给main()
函数.这允许我main
在sys
尚未导入的上下文中使用.
大多数情况下,这对于清晰和明智而言是有用的,但情况并非总是如此.以下是模块导入可能存在于其他地方的情况的几个示例.
首先,您可以使用具有以下形式的单元测试的模块:
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() [...]
在其他情况下,您可能会将导入放入代码中的其他部分.
当函数被调用为零或一次时,第一个变体确实比第二个更有效.但是,通过第二次和后续调用,"导入每次调用"方法实际上效率较低.请参阅此链接了解延迟加载技术,该技术通过执行"延迟导入"来结合两种方法中的最佳方法.
但除了效率之外还有其他原因,为什么你可能更喜欢一个而不是另一个.一种方法是让读取代码的人更清楚该模块具有的依赖关系.它们也有非常不同的故障特征 - 如果没有"datetime"模块,第一个将在加载时失败,而第二个在调用方法之前不会失败.
添加注意:在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
(回顾:John Millikin说的话.)
这就像许多其他优化一样 - 你牺牲了一些速度的可读性.正如约翰所提到的,如果你已经完成了你的剖析作业,并发现这是一个非常有用的变化,你需要额外的速度,那就去吧.把所有其他进口产品都记下来可能是件好事:
from foo import bar from baz import qux # Note: datetime is imported in SomeClass below