Python中的本地import语句

Dus*_*etz 44 python python-import

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

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

for i in xrange(10): Process()
Run Code Online (Sandbox Code Playgroud)

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

Lar*_*ngs 72

其他答案表明了如何import真正起作用的轻微混乱.

这个说法:

import foo
Run Code Online (Sandbox Code Playgroud)

大致相当于这个陈述:

foo = __import__('foo', globals(), locals(), [], -1)
Run Code Online (Sandbox Code Playgroud)

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

__import__()函数处理概念性转换的字符串('foo')到模块对象.模块被高速缓存sys.modules,这是第一个__import__()看起来 - 如果sys.modules有一个条目'foo',那__import__('foo')将返回什么,无论它是什么.它真的不关心类型.你可以自己看到这个; 尝试运行以下代码:

import sys
sys.modules['boop'] = (1, 2, 3)
import boop
print boop
Run Code Online (Sandbox Code Playgroud)

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

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

  • 它还偶尔会使您免于周期性导入(例如:如果您需要使用django在managers.py文件中导入模型,而models.py已经导入了managers.py文件......它通常会这样做) (17认同)
  • 除了明确的答案,我很高兴你使用3 _not_,因为2或4会令人困惑. (11认同)

And*_*are 12

请参阅PEP 8:

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

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

  • 很酷的链接,但是:"打破特定规则的两个很好的理由:(1)当应用规则时,即使对于习惯于阅读遵循规则的代码的人来说,代码的可读性也会降低." (3认同)
  • 这仅适用于顶级导入.在调用该函数之前,不会处理函数内部的导入.虽然我通常不鼓励这样做,但是如果您的依赖项加载非常昂贵或者可能无法在所有环境中使用,那么它会非常有用. (3认同)

Mar*_*off 10

除了风格之外,导入的模块确实只会导入一次(除非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        
Run Code Online (Sandbox Code Playgroud)

我不确定为虚拟机翻译多少字节码,但如果这是你程序的一个重要的内循环,那么你当然希望对该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
Run Code Online (Sandbox Code Playgroud)


Rob*_*ney 8

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

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

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

编辑:

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

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

知道了这一点,我觉得有点愚蠢地修复我修复它的代码中的所有地方,但那是我的交叉.