Python中的本地导入语句

我认为把导入语句放在使用它的片段附近,使得它的依赖关系更加清晰,从而有助于可读性。 Python会caching吗? 我应该在乎吗? 这是一个坏主意吗?

def Process(): import StringIO file_handle=StringIO.StringIO('hello world') #do more stuff for i in xrange(10): Process() 

更多的理由:这是使用库的神秘的方法,但是当我重构方法到另一个文件,我没有意识到我错过了外部依赖,直到我得到一个运行时错误。

其他答案表明,如何import确实有效。

这个说法:

 import foo 

大致相当于这个说法:

 foo = __import__('foo', globals(), locals(), [], -1) 

也就是说,它在当前作用域中创build一个名称与所请求模块相同的variables,并为其分配调用__import__()的结果与该模块名称和一个默认参数的__import__()

__import__()函数在概念__import__()string( 'foo' )转换为模块对象。 模块被caching在sys.modules ,这就是__import__()看起来的第一个地方 – 如果sys.modules有一个'foo'的入口,这就是__import__('foo')会返回的东西,不pipe它是什么。 它真的不关心这种types。 你可以亲自看到这一点。 尝试运行下面的代码:

 import sys sys.modules['boop'] = (1, 2, 3) import boop print boop 

暂时不考虑文体问题,在函数内部添加一个导入语句就可以达到你想要的效果。 如果之前从未导入模块,则将其导入并caching在sys.modules中。 然后将该模块分配给具有该名称的局部variables。 它不会修改任何模块级别的状态。 它可能会修改一些全局状态(向sys.modules添加一个新条目)。

这就是说,我几乎从不使用函数内部的import 。 如果导入模块会在程序中产生明显的减速,就像在静态初始化中执行长计算一样,或者它只是一个庞大的模块,而且程序很less实际上需要模块来完成任何事情,它使用的function。 (如果这太令人厌恶的话,Guido会跳进他的时间机器,改变Python来阻止我们这样做)。但是,通常我和一般的Python社区将所有的import语句放在模块范围的模块顶部。

请参阅PEP 8 :

导入总是放在文件的顶部,在任何模块注释和文档string之后,在模块全局variables和常量之前。

请注意,这纯粹是一种风格select,因为Python会将所有import语句视为相同,无论它们在源文件中声明的位置如何。 不过我会build议你遵循惯例,因为这会使你的代码更易读。

除了风格之外,导入的模块只能导入一次(除非在所述模块上调用reload )。 但是,每次调用import Foo都会隐式检查是否已经加载了该模块(通过检查sys.modules )。

另外考虑两个其他function相同的function的“反汇编”,其中一个试图导入一个模块,另一个不是:

 >>> def Foo(): ... import random ... return random.randint(1,100) ... >>> dis.dis(Foo) 2 0 LOAD_CONST 1 (-1) 3 LOAD_CONST 0 (None) 6 IMPORT_NAME 0 (random) 9 STORE_FAST 0 (random) 3 12 LOAD_FAST 0 (random) 15 LOAD_ATTR 1 (randint) 18 LOAD_CONST 2 (1) 21 LOAD_CONST 3 (100) 24 CALL_FUNCTION 2 27 RETURN_VALUE >>> def Bar(): ... return random.randint(1,100) ... >>> dis.dis(Bar) 2 0 LOAD_GLOBAL 0 (random) 3 LOAD_ATTR 1 (randint) 6 LOAD_CONST 1 (1) 9 LOAD_CONST 2 (100) 12 CALL_FUNCTION 2 15 RETURN_VALUE 

我不确定这个虚拟机翻译的字节码是多less,但是如果这是你程序的一个重要的内部循环,那么你肯定希望在Foo方法上加上一些Bar的方法。

当使用Bar时,一个快速和肮脏的timeittesting确实显示出适度的速度改进:

 $ python -m timeit -s "from a import Foo,Bar" -n 200000 "Foo()" 200000 loops, best of 3: 10.3 usec per loop $ python -m timeit -s "from a import Foo,Bar" -n 200000 "Bar()" 200000 loops, best of 3: 6.45 usec per loop 

我做了这个,然后希望我没有。 通常,如果我正在编写一个函数,并且该函数需要使用StringIO ,那么我可以查看模块的顶部,看看是否正在导入,然后添加它,如果没有。

假设我不这样做, 假设我在我的函数中本地添加它。 然后,假设在某个地方,我或其他人添加了一些使用StringIO的其他函数。 那个人要看模块的顶部并添加import StringIO 。 现在你的函数包含的代码不仅是意外的,而且是多余的。

另外,它违背了我认为是一个非常重要的原则:不要直接从函数内部修改模块级别的状态。

编辑:

事实上,所有这些都是无稽之谈。

导入一个模块不会修改模块级别的状态(它初始化被导入的模块,如果没有其他的东西,但是不完全相同的东西)。 导入一个你已经导入的模块,除了查找sys.modules和在本地范围内创build一个variables之外,你什么也得不到。

知道这一点,我觉得有些哑巴修理我的代码中的所有地方,我固定它,但这是我的十字架。

当Python解释器碰到一个导入语句时,它开始读取正在导入的文件中的所有函数定义。 这解释了为什么有时,import可能需要一段时间。

Andrew Hare指出,在开始阶段进行所有导入的想法是一个风格化的惯例。 但是,您必须记住,通过这样做,隐式地让解释器检查在第一次导入之后是否已经导入了该文件。 当您的代码文件变大并且您想“升级”您的代码以删除或replace某些依赖项时,这也会成为问题。 这将要求你search你的整个代码文件来查找你已经导入这个模块的所有地方。

我会build议按照惯例,并保持导入在您的代码文件的顶部。 如果你确实想跟踪函数的依赖关系,那么我build议将它们添加到该函数的文档string中。

当你需要在本地导入时,我可以看到两种方法

  1. 为了testing目的或临时使用,您需要导入一些东西,在这种情况下,您应该在使用地点进行导入。

  2. 有时为了避免循环依赖,你将需要在一个函数中导​​入它,但这意味着你有其他问题。

否则,为了效率和一致性,总是要放在最前面。