关于python:”import*”为什么不好?

Why is “import *” bad?

建议不要在python中使用import *

有人能告诉我原因吗?这样我下次就可以避免了。


  • 因为它在您的名称空间中放置了很多东西(可能会隐藏以前导入的其他对象,而您不知道)。

  • 因为您不知道确切导入了什么,也不容易找到从哪个模块导入了某个东西(可读性)。

  • 因为您不能使用像pyflakes这样的酷工具静态地检测代码中的错误。


根据《Python禅》:

Explicit is better than implicit.

…当然,不能反驳吗?


你没有把**locals()传递给函数,是吗?

由于python缺少一个"include"语句,并且self参数是显式的,并且作用域规则非常简单,所以通常很容易将手指指向一个变量,并告诉该对象来自何处——不读取其他模块,也不使用任何类型的IDE(不管怎样,这些都限制了自省的方式,因为事实上语言是非常动态的)。

import *打破了这一切。

此外,它还具有隐藏bug的具体可能性。

1
2
import os, sys, foo, sqlalchemy, mystuff
from bar import *

现在,如果BAR模块有"os"、"mystuff"等任何一个……属性,它们将覆盖显式导入的属性,并可能指向非常不同的内容。在BAR中定义__all__通常是明智的——这说明了将隐式导入的内容——但如果不读取和分析BAR模块并遵循其导入,仍然很难跟踪对象的来源。当我拥有一个项目的所有权时,我首先要修复的是import *的网络。

别误会我了:如果import *不见了,我会哭着要它。但必须小心使用。一个好的用例是在另一个模块上提供一个外观接口。同样,在函数/类名称空间中使用条件导入语句或导入也需要一些规则。

我认为在大中型项目中,或者有几个贡献者的小项目中,静态分析(至少运行pyflakes,或者更好的是正确配置的pylint)需要最低的卫生要求,以便在发生之前捕获几种错误。

当然,因为这是python——可以随意打破规则和探索——但是要小心可能增长十倍的项目,如果源代码缺少规则,这将是一个问题。


可以在交互式会话中执行from ... import *


这是因为您正在污染名称空间。您将导入自己名称空间中的所有函数和类,这可能与您自己定义的函数冲突。

此外,我认为使用限定名对于维护任务来说更清楚;您可以在代码行本身看到函数的来源,这样您可以更容易地签出文档。

在模块Fo:

1
2
def myFunc():
    print 1

在您的代码中:

1
2
3
4
5
6
7
from foo import *

def doThis():
    myFunc() # Which myFunc is called?

def myFunc():
    print 2


http://docs.python.org/tutorial/modules.html网站

Note that in general the practice of importing * from a module or package is frowned upon, since it often causes poorly readable code.


假设在名为foo的模块中有以下代码:

1
import ElementTree as etree

在你自己的模块中,你有:

1
2
from lxml import etree
from foo import *

您现在有一个很难调试的模块,看起来它包含lxml的etree,但实际上它包含elementtree。


这些都是很好的答案。我还要补充一点,在用Python教新人代码时,处理import *是非常困难的。即使你或他们没有写代码,这仍然是一个绊脚石。

我教孩子们(大约8岁)用Python编程来操纵雷艇。我喜欢为他们提供一个有用的编码环境来与(Atom编辑器)一起工作,并教授repl驱动的开发(通过bpython)。在Atom中,我发现提示/完成和bpython一样有效。幸运的是,与其他统计分析工具不同,Atom没有被import *愚弄。

然而,让我们举个例子……在这个包装器中,他们使用了一组模块,包括这个块列表。让我们忽略名称空间冲突的风险。通过执行from mcpi.block import *,他们会列出所有不清晰类型的块,您必须查看这些块才能知道可用的是什么。如果他们使用了from mcpi import block,那么您可以键入walls = block.,然后会弹出一个自动完成列表。Atom.io screenshot


理解人们在这里提出的有效观点。然而,我确实有一个论点,即有时,"星导入"并不总是一种坏做法:

  • 当我想要以这样一种方式构造我的代码时,所有的常量都会被放到一个名为const.py的模块中:
    • 如果我做import const,那么对于每个常量,我必须将其称为const.SOMETHING,这可能不是最方便的方法。
    • 如果我做from const import SOMETHING_A, SOMETHING_B ...,那么显然这太冗长了,并且破坏了结构化的目的。
    • 因此,我觉得在这种情况下,做一个from const import *可能是一个更好的选择。

作为测试,我创建了一个模块test.py,它有两个功能a和b,分别打印"a 1"和"b 1"。导入test.py后使用:

1
import test

. …我可以运行test.a()和test.b()这两个函数,"test"在命名空间中显示为一个模块,因此如果我编辑test.py,我可以用以下命令重新加载它:

1
2
import importlib
importlib.reload(test)

但如果我这样做:

1
from test import *

命名空间中没有对"test"的引用,因此在编辑之后(据我所知)无法重新加载它,这在交互式会话中是一个问题。鉴于以下任一项:

1
2
import test
import test as tt

将在命名空间中添加"test"或"tt"(分别)作为模块名,这将允许重新加载。

如果我这样做:

1
from test import *

名称"a"和"b"作为函数出现在命名空间中。如果我编辑test.py并重复上述命令,则不会重新加载修改后的函数版本。

下面的命令将引发一条错误消息。

1
importlib.reload(test)    # Error - name 'test' is not defined

如果有人知道如何重新加载加载了"从模块导入*"的模块,请发布。否则,这将是另一个避免使用表单的原因:

1
from module import *

这是一种非常糟糕的做法,原因有两个:

  • 代码可读性
  • 覆盖变量/函数等的风险
  • 对于点1:我们来看一个例子:

    1
    2
    3
    4
    5
    from module1 import *
    from module2 import *
    from module3 import *

    a = b + c - d

    在这里,当看到代码时,没有人会知道bcd实际上属于哪个模块。

    另一方面,如果你喜欢:

    1
    2
    3
    4
    5
    6
    #                   v  v  will know that these are from module1
    from module1 import b, c   # way 1
    import module2             # way 2

    a = b + c - module2.d
    #            ^ will know it is from module2

    它对你来说更干净,而且新加入你团队的人会有更好的想法。

    对于第2点:假设module1module2都有变量b。当我这样做的时候:

    1
    2
    3
    4
    from module1 import *
    from module2 import *

    print b  # will print the value from module2

    这里,module1的值丢失。即使在module1中声明了b并且我编写了希望我的代码使用module1.b的代码,也很难调试为什么代码不工作。

    如果您在不同的模块中有相同的变量,并且不想导入整个模块,您甚至可以这样做:

    1
    2
    from module1 import b as mod1b
    from module2 import b as mod2b


    如文档中建议的,在生产代码中不应使用import *

    虽然从模块导入*很糟糕,但从包导入*则更糟。基本上,from package import *导入由包的__init__.py定义的任何名称,但它还包括由以前的import语句加载的包的任何子模块。

    考虑这个例子:

    1
    2
    3
    4
    5
    6
    # anywhere in the code before import *
    import sound.effects.echo
    import sound.effects.surround

    # in your module
    from sound.effects import *

    最后一条语句将把echosurround模块导入当前命名空间(可能覆盖以前的定义),因为它们在执行import语句时在sound.effects包中定义。