Python中的本地import语句

时间:2009-11-09 04:34:04

标签: python python-import

我认为将import语句放在靠近使用它的片段上有助于通过使其依赖关系更加清晰来实现可读性。 Python会缓存吗?我应该关心吗?这是个坏主意吗?

def Process():
    import StringIO
    file_handle=StringIO.StringIO('hello world')
    #do more stuff

for i in xrange(10): Process()

更合理一点:它是用于使用库的神秘位的方法,但是当我将该方法重构为另一个文件时,我没有意识到我错过了外部依赖,直到我遇到运行时错误。

6 个答案:

答案 0 :(得分:69)

其他答案表明import真正起作用的方式存在轻微的混淆。

本声明:

import foo

大致相当于这句话:

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

也就是说,它在当前作用域中创建一个与所请求模块同名的变量,并为其分配调用__import__()的结果以及该模块名称和一大堆默认参数。

__import__()函数句柄在概念上将字符串('foo')转换为模块对象。模块缓存在sys.modules中,这是__import__()看起来的第一个地方 - 如果sys.modules有一个'foo'条目,那就是__import__('foo')将返回的内容,无论它是什么。它真的不关心类型。你可以自己看到这个;尝试运行以下代码:

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

暂时不考虑风格问题,在函数内部使用import语句可以达到您想要的效果。如果之前从未导入过该模块,则会在sys.modules中导入并缓存该模块。然后它将模块分配给具有该名称的局部变量。它不是修改任何模块级状态。它 可能会修改一些全局状态(向sys.modules添加一个新条目)。

那就是说,我几乎从不在函数中使用import。如果导入模块会在程序中产生明显的减速 - 就像它在静态初始化中执行一个长计算,或者它只是一个庞大的模块 - 并且你的程序实际上很少需要模块来做任何事情,只在里面导入它是完全没问题的。它使用的功能。 (如果这很令人反感,Guido会跳进他的时间机器并改变Python以防止我们这样做。)但作为一项规则,我和一般的Python社区将所有的import语句放在模块范围内的模块顶部。

答案 1 :(得分:10)

请参阅PEP 8

  

进口总是放在首位   文件,就在任何模块之后         注释和文档字符串,以及模块全局和常量之前。

请注意,这纯粹是一种风格选择,因为Python会将所有import语句视为相同,无论它们在源文件中的声明位置如何。我仍然建议您遵循惯例,因为这会使您的代码对其他人更具可读性。

答案 2 :(得分:9)

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

还考虑两个其他方面相同的函数的“反汇编”,其中一个尝试导入模块而另一个不尝试:

>>> 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        

我不确定为虚拟机翻译多少字节码,但如果这是您程序的一个重要内循环,那么您肯定希望对Bar方法进行一些重视。 Foo方法。

使用timeit时,快速而肮脏的Bar测试确实显示出适度的速度提升:

$ 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

答案 3 :(得分:8)

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

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

此外,它违反了我认为非常重要的原则:不要直接修改函数内部的模块级状态。

修改

实际上,事实证明以上所有都是无稽之谈。

导入模块修改模块级状态(它初始化正在导入的模块,如果还没有其他任何东西,但这根本不是同样的事情)。导入已经在其他地方导入的模块除了查找sys.modules并在本地范围内创建变量外,不需要任何费用。

知道了这一点,我觉得修复代码中的所有地方都很愚蠢,但这是我的十字架。

答案 4 :(得分:3)

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

开始时进行所有导入的想法是Andrew Hare指出的风格约定。但是,您必须记住,通过这样做,您隐式地使解释程序检查此文件是否在第一次导入后已导入。当您的代码文件变大并且您想要“升级”代码以删除或替换某些依赖项时,它也会成为一个问题。这将要求您搜索整个代码文件以查找导入此模块的所有位置。

我建议遵循约定并将导入保留在代码文件的顶部。如果你确实想要跟踪函数的依赖关系,那么我建议在docstring中为该函数添加它们。

答案 5 :(得分:0)

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

  1. 出于测试目的或临时使用,您需要导入一些东西,在这种情况下,您应该将导入放在使用地点。

  2. 有时为了避免循环依赖,您需要在函数中导入它,但这意味着您在其他地方遇到问题。

  3. 否则,为了提高效率和一致性,请始终将其放在首位。