Python import coding style
我发现了一种新的模式。 这种模式是众所周知的还是对它的看法是什么?
基本上,我很难刷新源文件以找出可用的模块导入等等,所以现在,而不是
1 2 3 4 5 | import foo from bar.baz import quux def myFunction(): foo.this.that(quux) |
我将所有导入移动到它们实际使用的函数中,如下所示:
1 2 3 4 5 | def myFunction(): import foo from bar.baz import quux foo.this.that(quux) |
这做了一些事情。 首先,我很少意外地用其他模块的内容污染我的模块。 我可以为模块设置
其次,我很少在我的模块顶部进行一连串的进口,其中一半或更多我不再需要,因为我已经重构了它。 最后,我发现这个模式更容易阅读,因为每个引用的名称都在函数体中。
这个问题的(先前)最高投票答案格式很好,但性能绝对错误。让我来证明一下
性能
顶级导入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import random def f(): L = [] for i in xrange(1000): L.append(random.random()) for i in xrange(1000): f() $ time python import.py real 0m0.721s user 0m0.412s sys 0m0.020s |
在函数体中导入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | def f(): import random L = [] for i in xrange(1000): L.append(random.random()) for i in xrange(1000): f() $ time python import2.py real 0m0.661s user 0m0.404s sys 0m0.008s |
如您所见,在函数中导入模块可能更有效。这样做的原因是简单的。它将引用从全局引用移动到本地引用。这意味着,至少对于CPython,编译器将发出
通常,最好在顶部导入,但如果您多次访问模块,性能不是原因。原因是人们可以更容易地跟踪模块所依赖的内容,并且这样做与Python Universe的其余部分一致。
这确实有一些缺点。
测试
如果您想通过运行时修改来测试模块,可能会使其变得更加困难。而不是做
1 2 | import mymodule mymodule.othermodule = module_stub |
你必须这样做
1 2 | import othermodule othermodule.foo = foo_stub |
这意味着您必须全局修补othermodule,而不是仅仅更改mymodule中的引用指向的内容。
依赖性跟踪
这使得模块所依赖的模块不明显。如果您使用许多第三方库或重新组织代码,这尤其令人恼火。
我不得不维护一些遗留代码,这些代码在整个地方使用了内联导入,这使得代码极难重构或重新打包。
关于表现的说明
由于python缓存模块的方式,没有性能损失。实际上,由于模块位于本地名称空间中,因此在函数中导入模块会有一些性能上的好处。
顶级导入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import random def f(): L = [] for i in xrange(1000): L.append(random.random()) for i in xrange(10000): f() $ time python test.py real 0m1.569s user 0m1.560s sys 0m0.010s |
在函数体中导入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | def f(): import random L = [] for i in xrange(1000): L.append(random.random()) for i in xrange(10000): f() $ time python test2.py real 0m1.385s user 0m1.380s sys 0m0.000s |
这种方法存在一些问题:
- 打开文件所依赖的模块并不是很明显。
-
它会混淆必须分析依赖关系的程序,例如
py2exe ,py2app 等。 - 您在许多功能中使用的模块怎么样?您最终会得到大量的冗余导入,或者您必须在文件的顶部和一些内部函数中放置一些。
所以...首选的方法是将所有导入放在文件的顶部。我发现如果我的导入很难跟踪,通常意味着我有太多的代码,我最好把它分成两个或更多的文件。
在某些我发现函数内部导入有用的情况:
- 处理循环依赖(如果你真的无法避免它们)
- 平台特定代码
另外:在每个函数中放入导入实际上并不比文件顶部慢。第一次加载每个模块时,它被放入
另一个需要注意的有用的事情是在Python 3.0中删除了函数内部的
这里简单提一下"删除的语法":
http://docs.python.org/3.0/whatsnew/3.0.html
我建议你尽量避免
在导入包的所有其他位置,只需使用
如果
人们已经很好地解释了为什么要避免内联导入,而不是真正的替代工作流来解决你想要它们的原因。
I have a hard time scrubbing up and down source files to figure out what module imports are available and so forth
要检查未使用的导入,我使用pylint。它执行静态(ish) - Python代码分析,它检查的(很多)事情之一是未使用的导入。例如,以下脚本..
1 2 3 4 | import urllib import urllib2 urllib.urlopen("http://stackoverflow.com") |
..将生成以下消息:
1 | example.py:2 [W0611] Unused import urllib2 |
至于检查可用的导入,我通常依赖于TextMate(相当简单)的完成 - 当你按Esc时,它会在文档中与其他人完成当前的单词。如果我已经完成
两种变体都有其用途。但是在大多数情况下,最好在函数之外导入,而不是在函数内部导入。
性能
在几个答案中已经提到过,但在我看来,他们都缺乏完整的讨论。
第一次在python解释器中导入模块时,无论它是在顶级还是在函数内部,它都会很慢。它很慢,因为Python(我专注于CPython,它可能与其他Python实现不同)做了多个步骤:
后续导入不必执行所有这些操作,因为Python只能从
可能是模块中的函数实际上并未经常使用,但它取决于
但是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import random import itertools def func_1(): return random.random() def func_2(): import random return random.random() def loopy(func, repeats): for _ in itertools.repeat(None, repeats): func() %timeit loopy(func_1, 10000) # 1.14 ms ± 20.6 μs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit loopy(func_2, 10000) # 2.21 ms ± 138 μs per loop (mean ± std. dev. of 7 runs, 100 loops each) |
这几乎慢了两倍。
非常重要的是要意识到aaronasterling在答案中"作弊"了一下。他表示,在函数中进行导入实际上会使函数更快。在某种程度上,这是事实。那是因为Python如何查找名称:
因此,不是检查本地范围然后检查全局范围,而是检查本地范围,因为模块的名称在本地范围内可用。这实际上使它更快!但这是一种称为"循环不变代码运动"的技术。它基本上意味着通过在循环(或重复调用)之前将其存储在变量中来减少循环(或重复)中完成的事情的开销。因此,除了在函数中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | import random import itertools def f1(repeats): "Repeated global lookup" for _ in itertools.repeat(None, repeats): random.random() def f2(repeats): "Import once then repeated local lookup" import random for _ in itertools.repeat(None, repeats): random.random() def f3(repeats): "Assign once then repeated local lookup" local_random = random for _ in itertools.repeat(None, repeats): local_random.random() %timeit f1(10000) # 588 μs ± 3.92 μs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit f2(10000) # 522 μs ± 1.95 μs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit f3(10000) # 527 μs ± 4.51 μs per loop (mean ± std. dev. of 7 runs, 1000 loops each) |
虽然您可以清楚地看到对全局
这可以通过避免循环内的函数查找来达到极限:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | def f4(repeats): from random import random for _ in itertools.repeat(None, repeats): random() def f5(repeats): r = random.random for _ in itertools.repeat(None, repeats): r() %timeit f4(10000) # 364 μs ± 9.34 μs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit f5(10000) # 357 μs ± 2.73 μs per loop (mean ± std. dev. of 7 runs, 1000 loops each) |
再快得多,但导入和变量之间几乎没有差别。
可选的依赖项
有时进行模块级导入实际上可能是个问题。例如,如果您不想添加另一个安装时依赖项,但该模块对某些其他功能非常有帮助。决定依赖项是否应该是可选的不应该轻易完成,因为它会影响用户(如果他们得到意外的
但是,这可以通过两种方式完成:
1 2 3 4 5 6 7 | try: import matplotlib.pyplot as plt except ImportError: pass def function_that_requires_matplotlib(): plt.plot() |
要么:
1 2 3 | def function_that_requires_matplotlib(): import matplotlib.pyplot as plt plt.plot() |
通过提供替代实现或自定义用户看到的异常(或消息),可以更加自定义,但这是主要要点。
如果想要为可选依赖项提供替代"解决方案",那么顶级方法可能会更好一些,但通常人们使用函数内导入。主要是因为它导致更清晰的堆栈跟踪并且更短。
循环进口
函数内导入可以非常有助于避免由于循环导入导致的ImportErrors。在很多情况下,圆形进口是"坏"包装结构的标志,但如果绝对没有办法避免循环导入,则通过将导入圆圈的进口放入"循环"(以及问题)来解决实际使用它的功能。
不要重复自己
如果您实际将所有导入放在函数而不是模块范围中,则会引入冗余,因为函数可能需要相同的导入。这有一些缺点:
其他想法:
I rarely end up with a litany of imports at the top of my modules, half or more of which I no longer need because I've refactored it.
Ok.
大多数IDE已经有一个未使用导入的检查器,因此可能只需点击几下就可以删除它们。即使您不使用IDE,也可以偶尔使用静态代码检查器脚本并手动修复它。另一个答案提到了pylint,但还有其他答案(例如pyflakes)。
I rarely accidentally pollute my modules with the contents of other modules
Ok.
这就是为什么你通常使用
此外,如果您认为您过多地污染了模块名称空间,那么您可能应该考虑将模块拆分为子模块,但这只对几十个导入有意义。
如果你想减少命名空间污染,另外一个(非常重要的)要点是避免
作为最后的手段,您始终可以使用别名来避免使用"
摘要
性能影响是可见的,但几乎总是微观优化,所以不要让决定在哪里进行微观基准测试。除非第一个
使用通常理解的工具来定义公共API,我的意思是
你更喜欢哪一个并不重要,两者都有效。如果你是独自工作,你可以推断出利弊,做一个你认为最好的。但是,如果你在一个团队中工作,你可能应该坚持使用已知模式(这将是
好。
您可能想要查看python wiki中的Import语句开销。简而言之:如果模块已经加载(查看
我相信在某些情况/场景中这是一种推荐的方法。例如,在Google App Engine中建议使用延迟加载大模块,因为它可以最大限度地降低实例化新Python VM /解释器的预热成本。看一下Google Engineer的演示文稿,描述这一点。但请记住,这并不意味着您应该延迟加载所有模块。
从性能的角度来看,您可以看到:Python import语句是否始终位于模块的顶部?
一般来说,我只使用本地导入来打破依赖循环。
安全实施
考虑一个环境,其中所有Python代码都位于只有特权用户才能访问的文件夹中。为了避免以特权用户身份运行整个程序,您决定在执行期间将权限删除给非特权用户。一旦您使用导入另一个模块的函数,您的程序将抛出