为什么需要使用全名时还要进行导入?

10

在Python中,如果你需要来自不同包的模块,你必须先导入它。对于来自Java背景的人来说,这是很合理的。

import foo.bar

然而,令人困惑的是每次想要使用bar时为什么需要使用完整名称?如果我想使用完整名称,为什么还需要进行导入操作呢?不是直接使用完整名称就可以描述我正在使用哪个模块吗?

这似乎有点多余,因为import foo.bar应该已经做了from foo import bar所做的事情。同时也有点含糊,因为我打算使用完整名称时为什么还需要导入。


1
“需要导入”是什么意思?你希望在没有先导入它的情况下,foo.bar能够正常工作吗?那么你怎么知道foo是一个模块而不是一个普通变量呢? - mb14
4
在Java中,如果您在代码中明确使用完全限定类名和包名,您可以直接使用另一个包中的类,而无需导入。例如,您可以将Java的List接口称为“java.util.List”,或者首先导入它,然后仅将其称为“List”。对于从Java转来的人,Python的方式感觉像是导入了"java.util.List",然而在您的代码中仍需要写出"java.util.List"而不是"List"。 - Jeff
8个回答

25

事实上,尽管Python的import语句看起来与Java的相似,但它们在底层执行完全不同的操作。正如您所知,在Java中,import语句仅仅是对编译器的一种提示。它基本上为完全限定类名设置了一个别名。例如,当你写下:

import java.util.Set;

它告诉编译器在该文件中,当你写 Set 时,你指的是 java.util.Set。如果你写了 s.add(o),其中 s 是类型为 Set 的对象,那么编译器(或链接器)会查找 Set.class 中的 add 方法并将其引用插入其中。

但在Python中,

import util.set

(顺便说一下,那是一个虚构的模块)执行了完全不同的操作。在Python中,包和模块不仅仅是名称,它们实际上是对象。当你在代码中写util.set时,这会告诉Python去访问一个名为util的对象并查找其上名为set的属性。Python的import语句的工作是创建该对象和属性。它的工作原理是解释器寻找一个名为util/__init__.py的文件,使用其中的代码来定义对象的属性,并将该对象绑定到名称util上。类似地,util/set.py中的代码将用于初始化一个对象,该对象绑定到util.set上。有一个名为__import__的函数来处理所有这些,实际上import util.set语句基本上相当于:

util = __import__('util.set')
当你导入一个Python模块时,获得的是对应顶级包'util'的对象。要访问util.set,需要通过这个对象,这就是为什么在Python中似乎需要使用完全限定名称的原因。
当然,也有办法规避这个问题。由于所有这些东西都是对象,一种简单的方法是将util.set绑定到一个更简单的名称上。也就是说,在import语句后,可以这样写:
set = util.set

从那个时候起,您只需在原本编写util.set的地方使用set即可。(当然,这会混淆内置的set类,所以我不建议实际使用set这个名称。)或者,如至少另一个答案中提到的那样,您可以编写

from util import set

或者

import util.set as set

这仍然导入了包含模块setutil包,但是它不会在当前作用域中创建一个util变量,而是创建一个set变量,它指向util.set。在幕后,这种方法的工作方式有点像:

_util = __import__('util', fromlist='set')
set = _util.set
del _util

在前一种情况下,或者

_util = __import__('util.set')
set = _util.set
del _util

尽管两种方式都实现了相同的功能,但后一种形式在语义上更像Java的import语句:它定义了一个别名(set),用于访问通常只能通过完全限定名称(util.set)才能访问的内容。


6
您可以缩短它,如果您愿意:
import foo.bar as whateveriwant

使用完整的名称可以避免具有相同命名子模块的两个软件包互相覆盖。

1
+1, 这是OP所寻找的正确答案。或许编辑一下,改成“bar”,而不是“whateveriwant”会更清晰地表明它提供了所需的功能。 - Cam
3
接近正确答案,但没有解释为什么Python表现出这种行为。 - Philipp
1
它也比 from foo import bar 稍微冗长一些。"import foo.bar as bar" == 21 个字符,"from foo import bar" == 19 个字符。 - JAB
1
@JAB 但是你可以使用 "import foo.bar as mybar" 来匹配你已经有的任何内部命名。 - Martin Beckett
2
@Martin Beckett:只有当bar是一个模块时,import a.b as c语法才能生效。如果b是一个方法或变量,则该语法无法使用。实际上,您无法使用点符号导入方法或变量。您必须使用from ... import ...语法,或通过包含模块名称引用该方法或变量。 - JAB

4

标准库中有一个名为io的模块:

In [84]: import io

In [85]: io
Out[85]: <module 'io' from '/usr/lib/python2.6/io.pyc'>

scipy 中还有一个叫做 io 的模块:

In [95]: import scipy.io

In [96]: scipy.io
Out[96]: <module 'scipy.io' from '/usr/lib/python2.6/dist-packages/scipy/io/__init__.pyc'>

如果您想在同一脚本中使用这两个模块,那么命名空间是区分它们的一种方便方法。
In [97]: import this
The Zen of Python, by Tim Peters
...
Namespaces are one honking great idea -- let's do more of those!

3
您对Python的导入方式有些困惑。(我刚开始也是这样。)在Python中,您不能像Java那样通过全名直接引用模块中的内容;无论您打算如何引用导入的项目,都必须首先导入该模块。尝试在解释器中输入math.sqrt(5),而不先导入mathmath.sqrt,看看会发生什么。

无论如何... import foo.bar要求您使用foo.bar而不是只使用bar,其原因是为了防止意外的命名空间冲突。例如,如果您执行import foo.bar,然后执行import baz.bar呢?

当然,您可以选择使用import foo.bar as bar(即别名),但如果您这样做,您可能会选择使用from foo import bar。(编辑:除非您想要导入方法和变量。那时您必须使用from ... import ...语法。这包括您想要导入一个方法或变量而不使用别名的情况,例如,如果bar是一个方法或变量,您不能简单地执行import foo.bar。)


“import foo.bar as bar” 似乎因为某些我无法找到的技术原因而优于“from foo import bar”。 - Philipp
1
@Philipp:如果是这样,那对我来说是新闻。 - David Z
1
@Philipp:http://docs.python.org/py3k/howto/doanddont.html#from-module-import-name1-name2 那里没有提到任何偏好。事实上,在某些情况下,您不能使用前者,因为当您尝试导入方法或数据成员时它会失败。 - JAB
有关以特殊方式修改成员的问题已经存在了一段时间。当使用 from 并在导入的模块中进行某些修改时,据我所记,这种更改不会传递到导入该模块的其他模块。但我可能是在凭空想象并混淆了什么,我一直无法找到证明。 - Philipp
1
@Philipp:你说得没错。看看我在之前评论中链接的页面。但是你不应该使用from来导入模块,因为它的主要目的是用于方法和变量/常量,这些不能直接使用通常的import语法导入。 - JAB

3
在Python中,导入不仅表示你可能会使用某些东西。实际上,导入在模块级别执行代码。您可以将导入视为函数被“解释”和创建的时刻。任何位于_____init_____.py级别或不在函数或类定义内部的代码都会在此时发生。
导入还会在文件/模块/任何地方将整个模块名称空间的廉价副本放入导入它的文件/模块/任何地方的名称空间中。然后,IDE就有了您可能开始键入以进行命令完成的函数列表。

请注意,当一个模块或包被多次导入时,模块或包中的任何可执行代码以及__init__.py文件中的代码仅在第一次导入时执行。这很好。 - JAB

3
Python哲学的一部分是显式优于隐式。如果Python在您尝试访问包中的某些内容时自动导入,那么这不是显式的。我还猜测,如果导入是自动的,那么包初始化会更加困难,因为代码中不会一致地执行导入。

1

您不必使用全名。可以尝试使用以下其中一个

from foo import bar

import foo.bar as bar

import foo.bar
bar = foo.bar

from foo import *

一些明确导入的好处:
  • 它们有助于向人类和工具表示模块依赖的包。
  • 它们避免了在运行时动态确定必须加载(可能编译)哪些软件包的开销。
  • 它们(连同sys.path)可以明确地将来自不同命名空间的具有冲突名称的符号区分开。
  • 它们使程序员能够控制进入其所使用的命名空间内的内容。

1

除了Java之外,在Python中,import foo.bar声明你将使用由foo.bar引用的东西。

这符合Python的哲学,即显式优于隐式。有更多的编程语言比Java更明确地表达模块间依赖关系,例如Ada。

使用完整名称可以消除来自不同模块的具有相同名称的定义的歧义。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接