56

我认为将 import 语句放置在使用它的片段附近,通过使其依赖关系更加清晰来提高可读性。Python会缓存这个吗?我应该关心吗?这是一个坏主意吗?

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

for i in xrange(10): Process()

更多理由:它适用于使用库中神秘位的方法,但是当我将方法重构到另一个文件中时,我没有意识到我错过了外部依赖项,直到出现运行时错误。

4

6 回答 6

83

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 语句放在模块范围内的模块顶部。

于 2009-11-11T16:31:37.797 回答
14

请参阅PEP 8

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

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

于 2009-11-09T04:35:55.333 回答
13

抛开风格不谈,导入的模块确实只会被导入一次(除非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
于 2009-11-09T04:41:17.387 回答
8

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

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

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

编辑:

实际上,事实证明,以上所有内容都是胡说八道。

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

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

于 2009-11-09T22:03:22.167 回答
3

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

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

我建议遵循约定并将导入保持在代码文件的顶部。如果您确实想跟踪函数的依赖关系,那么我建议将它们添加到该函数的文档字符串中。

于 2009-11-09T04:45:57.907 回答
1

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

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

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

否则,为了效率和一致性,总是把它放在首位。

于 2009-11-09T05:03:23.003 回答