2009-11-09 70 views
37

我认为将导入语句放在使用它的片段附近可以使其依赖关系更加清晰,从而提高可读性。 Python会缓存这个吗?我应该在乎吗?这是一个坏主意吗?Python中的本地导入语句

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

for i in xrange(10): Process() 

多一点的理由:这是对于使用该库的神秘位方法,但是当我重构方法到另一个文件,我不知道我错过了外部依赖,直到我得到一个运行时错误。

+0

这将是一个非常好的“with(import StringIO)as moduleName:syntax – JeremyKun 2012-02-07 21:31:37

+0

@Bean:如果这样你不必输入长或笨拙的模块名称,那么有一个简单的方法:''导入StringIO'然后'sio = StringIO',现在你可以执行'file_handle = sio.StringIO('hello world')'并保存那些宝贵的五个字符,但我会尽量少用这个,因为它可以使代码变得更难(这项任务很容易被忽略;非标准的模块名称可能会让人分心) – cvoinescu 2012-05-02 21:30:32

回答

63

其他答案表明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社区将所有导入语句放在模块范围的模块顶部。

+15

它也偶尔可以将您从周期性导入中解救出来(例如:如果您需要使用django导入您的managers.py文件中的模型,并且models.py已经导入了管理器.py文件,通常它会这样做) – Jiaaro 2011-05-26 20:01:53

+4

明确的答案,我很高兴你用3 _not_,因为2或4会令人困惑。 – matiasg 2014-12-03 20:39:15

10

请参阅PEP 8

进口量始终把在 顶部的文件,只是任何模块后 意见和文档字符串,以及模块全局变量和常量之前。

请注意,这纯粹是一种文体选择,因为Python将所有import语句视为相同,无论它们在源文件中声明的位置如何。不过我会建议你遵循常规做法,因为这会使你的代码更易读。

+2

很酷的链接,但是:“打破特定规则的两个很好的理由:(1)当应用规则会使代码变得不可读时,即使对于习惯阅读遵循规则的代码的人也是如此。 ...“ – 2009-11-09 04:37:15

+0

这只适用于顶层导入,函数内部的导入只有在该函数被调用之后才会被处理,但我通常不鼓励这样做,当您有依赖关系时加载的代价非常昂贵,或者在所有环境中都不可用 – 2017-06-12 16:57:59

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   

我不知道字节码得到多少转化为虚拟机,但如果这是您程序的重要内部循环,那么您肯定希望通过Foo方法对Bar方法施加一些权重。

一个快速和肮脏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

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

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

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

+1

“当python解释器碰到一个导入语句时,它开始读取文件中的所有函数定义” - (1)它只发生在模块的第一次在Python解释器的当前运行期间被导入(并且导入的模块被存储在sys.modules中,以便下次仅需要名称查找(2)“读取函数定义”不是它所做的;它执行所有模块(大部分是'def'和'class'语句)。其他的东西,比如更多的import,设置模块级的数据结构(有时来自文件)可能需要一段时间。(3)不相关。 – 2009-11-10 09:27:52

0

我可以看到两种方式,当你需要在本地

  1. 导入用于测试目的或临时使用,你需要输入什么,在这种情况下,你应该把进口在使用的地方。

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

否则为了效率和一致性,总是将其置于顶端。

8

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

假设我不这样做;假设我在本地函数中添加它。然后假设在某个地方,我或其他人添加了一堆使用StringIO的其他函数。该人将看到模块的顶部并添加import StringIO。现在你的函数包含的代码不仅意想不到,而且是多余的。另外,它违背了我认为是一个非常重要的原则:不要直接从函数内部修改模块级别的状态。

编辑:

其实,事实证明,所有上述是无稽之谈。

导入模块不会修改模块级别的状态(它初始化被导入的模块,如果没有其他东西,但这不完全相同)。导入您已经导入的其他模块除了查找sys.modules以及在本地范围内创建变量外,其他任何操作都不会成本。

知道了这一点,我觉得有点傻,修正了我的代码中我固定它的所有地方,但那是我的交叉点。

+1

我粗体给你看使用我没有想到这一点,回想起来这真的很明显。 – 2009-11-10 01:28:58

+0

如果通过这个,你的意思是“在函数中间导入StringIO”会改变函数定义的模块范围的任何内容......这是错误的。正如我在我的答案中所解释的,在函数中间执行“导入”并不会改变定义该函数的模块状态中的anyuthing。 无论如何修改模块级别的状态有什么问题吗?有一个完整的关键词专注于做这件事:“全球化”。 – 2009-11-11 23:06:44

+0

+1为坦率。 – 2011-03-03 18:21:32