导入路径 - 正确的方法?

43 投票
3 回答
59986 浏览
提问于 2025-04-16 20:12

我知道有很多类似的问题,但我还是搞不懂该怎么使用模块。Python是我最喜欢的编程语言,除了处理导入(import)的部分,我对其他的都很喜欢:比如递归导入(当你试图引用一个还不存在的名字时),导入路径等等。

所以,我的项目结构是这样的:

my_project/
    package1/
        __init__.py
        module1
        module2
    package2/
        __init__.py
        module1
        module2

Package1可以单独使用,但也需要被package2导入。

现在我做的是,在package1.module1中写from package1 import module2,也就是使用完整路径来导入模块。我这样做是因为如果我用import module2,在从其他包(package2)导入时就会出问题。我也不能用from . import module2,因为直接运行module1时也会出错。

所以,为了让from package1 import module2在两种情况下都能工作(直接运行package1.module1和从package2导入时),我在package1.module1的开头加了这些行:

import os, sys
currDir = os.path.dirname(os.path.realpath(__file__))
rootDir = os.path.abspath(os.path.join(currDir, '..'))
if rootDir not in sys.path: # add parent dir to paths
    sys.path.append(rootDir)

对我来说这样可以,但我觉得这不是很符合Python的风格。我是不是做错了什么?

我是不是应该总是从项目根目录运行package1.module1?如果是这样的话,从IDE运行就不太方便了——我需要在IDE中设置路径。

更新:我试着在package1目录下添加一个root.pth文件,内容是..。但这没有用——我想这可能是用来做其他事情的。

结论:

  1. 总是使用绝对导入:import package1.module1

  2. 在根文件夹添加一个引导程序(bootstrapper),以便将某些模块作为独立脚本运行。这解决了从IDE运行脚本的问题,并且是符合Python风格的做法。

在2007年4月22日,Brett Cannon写道:

这个PEP是为了将if __name__ == "__main__": ...的写法改为if __name__ == sys.main: ...,这样你至少有机会在使用相对导入的包中执行模块。

我把这个PEP发给了python-ideas讨论。当讨论变得太多新想法时,我就停止了讨论。=) 我把所有这些想法列在了被拒绝的想法部分,虽然如果有强烈支持其中某个想法的声音,PEP可以转向其中之一。

我对这个以及其他任何对__main__机制的修改持反对态度。唯一的用例似乎是运行恰好在模块目录中的脚本,而我一直认为这是反模式。如果你想让我改变主意,你得说服我这不是反模式。

--Guido van Rossum

3 个回答

1

我来晚了五年……不过你只需要这样做:export PYTHONPATH=/path1:/path2:(注意最后有个冒号“:”)——这样你的工作目录(也就是你运行python的地方)就会被包含在路径里。

6

我通常会把每个包都做成可以安装的包,也就是创建一个setup.py文件。然后我会把这些包用pip安装到一个专门为这个项目创建的虚拟环境里。

如果这些包还在开发中,你甚至可以用pip -e来安装它们。

38

你的程序的入口点是什么?通常,程序的入口点会在项目的根目录下。因为它在根目录,所以根目录下的所有模块都可以被导入,只要它们里面有一个叫做 __init__.py 的文件。

所以,按照你的例子:

my_project/
    main.py
    package1/
        __init__.py
        module1
        module2
    package2/
        __init__.py
        module1
        module2

main.py 就是你程序的入口点。因为作为主程序执行的文件会自动被放到 PYTHONPATH 里,所以 package1package2 都可以从顶层导入。

# in main.py
from package1.module1 import *
from package1.module2 import *

# in package1.module1
import module2
from package2.module1 import *

# in package2.module1 import *
import module2
from package1.module1 import *

注意,上面提到的 package1 和 package2 是相互依赖的。这种情况是绝对不应该出现的。但这只是一个例子,说明可以从任何地方导入。

main.py 也不需要很复杂。它可以非常简单:

# main.py

if __name__ == '__main__':
    from package1.module1 import SomeClass
    SomeClass().start()

我想表达的重点是,如果一个模块需要被其他模块访问,那么这个模块应该作为顶层导入可用。一个模块不应该试图把自己放到顶层导入(直接放在 PYTHONPATH 上)。

确保所有导入都能被满足的责任在于项目,如果模块直接包含在项目中。实现这一点有两种方法。第一种是在项目文件夹中创建一个引导文件,比如 main.py。另一种是创建一个文件,将所有相关路径添加到 PYTHONPATH,这个文件会被任何可能存在的入口点加载。

例如:

# setup.py
import sys

def load():
    paths = ['/path1/','/path2/','/path3/']
    for p in path:
        sys.path.insert(0, p)

# entrypoint.py
from setup import load
load()
# continue with program

最重要的是,一个模块不应该自己放到路径上。路径应该由程序的入口点自动确定,或者通过一个知道所有相关模块位置的设置脚本明确定义。

撰写回答