为什么“import module”和“from package import module”会再次加载模块?

14 投票
2 回答
2506 浏览
提问于 2025-04-16 19:11

我在我的PYTHONPATH里有一个包,结构大概是这样的:

package/
    __init__.py
    module.py
        print 'Loading module'

如果我在package/这个目录下运行Python(或者在这个目录下写另一个模块),然后输入

import module

它会加载module.py,并且如预期那样打印出“Loading module”。但是,如果我接着输入

from package import module

它又会加载module.py,并且再次打印“Loading module”,这让我有点意外。我想知道这是为什么?

注意:我觉得我大概明白Python为什么这样做,因为对于import module来说,sys.modules的键就是"module",但对于from package import module来说,它的键是"package.module"。所以我想知道的是,为什么这里的键会不同——为什么不使用文件的路径名作为键,这样Python就能按预期工作了呢?

2 个回答

2

这是当前模块系统的一个小缺陷。

当你导入 module 时,是从当前的命名空间导入,而这个命名空间没有名字。这个命名空间里的值和 package 里的值是一样的,但解释器却无法识别。

当你导入 package.module 时,你是从 package 的命名空间导入模块。

这就是为什么 main.py 应该放在包文件夹外面的原因。很多模块的组织结构是这样的:

package /
    main.py
    package /
        sub_package1/
        sub_package2/
        sub_package3/
        module1.py
        module2.py

只调用 main.py 可以确保命名空间设置正确,也就是说 当前的命名空间是 main.py 的。这样就无法在 module2.py 中调用 import module1.py。你需要调用 import package.module1。这样做让事情变得更简单、更统一。

而且,是的,把当前文件夹当作没有名字的文件夹来导入确实是个坏主意。如果你有几个脚本,这会让人很头疼。但因为 Python 最开始就是这样设计的,所以也不是完全没有道理。

2

简单来说,当你从 package 目录运行代码时,你把 Python 配置搞错了。你不应该把这个目录放到 sys.path 中,因为它是在一个包里面的。

Python 不会把文件名当作关键字来用,因为它不是在导入一个文件,而是在导入一个模块。如果允许人们像这样写 'import c:\jim\my files\projects\code\stuff',那就会引发很多麻烦。

再考虑一个例子:假设你在 ~/foo/package/ 目录,而 ~/barPYTHONPATH 中,但 ~/bar 其实只是指向 ~/foo 的一个符号链接。你希望 Python 能帮你处理这个符号链接,去掉重复的部分吗?如果你把一个相对目录放到 PYTHONPATH 中,然后又换了目录呢?再比如,如果 'foo.py' 是指向 'bar.py' 的符号链接,你希望这两个也被去重吗?如果它们不是符号链接,而是完全相同的文件呢?在模糊的情况下加上复杂的规则,可能会让某些人觉得很方便,但对其他人来说却很麻烦。(Python 的哲学第12条:面对模糊,拒绝猜测的诱惑。)

Python 在这里做得很简单,你需要确保环境设置正确。现在,你可以说默认把当前目录放到 PYTHONPATH 中并不是个好主意,我可能也会同意你,但既然它在那儿,就应该遵循和其他路径条目一样的一致规则。如果你的应用程序是打算从任意目录运行的,可以通过在开始时执行 sys.path.remove('') 来移除当前目录。

撰写回答