问题描述

有如下项目目录:

website-monitor/
├── app/
│   ├── __init__.py
│   ├── models.py
│   ├── routes.py
│   ├── utils.py
│   └── templates/
│       └── index.html
├── scripts/
│   └── init_db.py
├── websites.json
├── requirements.txt
└── Dockerfile

运行项目前,需要提前使用scripts/init_db.py程序来初始化数据库。在初始化代码中引用了上级目录中的模块,如下:

from app.models import Database

def init_db():
    db = Database(host="db", user="vscode", password="vscode", dbname="website_monitor")
    db.execute("""
        CREATE TABLE IF NOT EXISTS performance_data (
            id SERIAL PRIMARY KEY,
            site TEXT NOT NULL,
            page_load_time INTEGER,
            ttfb INTEGER,
            resource_load_time INTEGER,
            dom_render_time INTEGER,
            timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        );
    """)
    db.close()

if __name__ == "__main__":
    init_db()

如果没有特别配置的话,有可能会提示找不到模块的错误。

$→:python3 init_db.py   
Traceback (most recent call last):
  File "/workspace/scripts/init_db.py", line 1, in <module>
    from app.models import Database
ModuleNotFoundError: No module named 'app'

这个错误是由于python无法找到app目录,无法正确引用app模块。

解决方案

为了解决这个问题,需要修改导入语句,将项目根目录添加到sys.path。在init_db.py 中添加以下代码即可以解决引入问题:

import sys
import os

# 将项目根目录添加到 Python 路径
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

前提条件是在app目录下应当有__init__.py文件,表明这是个包文件。

详细解释

sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 这行代码的作用是将项目根目录添加到 Python 的模块搜索路径中,以便 Python 能够正确找到并导入项目中的模块(如 app 模块)。


1. __file__

  • __file__ 是一个特殊变量,表示当前脚本的文件路径。

  • 例如,如果 init_db.py 的路径是 /workspace/app/scripts/init_db.py,那么 __file__ 的值就是 "/workspace/app/scripts/init_db.py"

2. os.path.abspath(__file__)

  • os.path.abspath() 函数用于获取文件的绝对路径。

  • 例如,如果 __file__"init_db.py",那么 os.path.abspath(__file__) 会返回 "/workspace/app/scripts/init_db.py"

3. os.path.dirname(path)

  • os.path.dirname() 函数用于获取路径的父目录。

  • 例如:

    • os.path.dirname("/workspace/app/scripts/init_db.py") 返回 "/workspace/app/scripts"

    • os.path.dirname("/workspace/app/scripts") 返回 "/workspace/app"

4. os.path.dirname(os.path.dirname(path))

  • 这里嵌套调用了两次 os.path.dirname(),目的是获取当前脚本的祖父目录(即项目根目录)。

  • 例如:

    • os.path.dirname("/workspace/app/scripts/init_db.py") 返回 "/workspace/app/scripts"

    • os.path.dirname("/workspace/app/scripts") 返回 "/workspace/app"

5. sys.path.append(path)

  • sys.path 是 Python 的模块搜索路径列表。Python 在导入模块时,会按照 sys.path 中的路径顺序查找模块。

  • sys.path.append(path) 将指定的路径添加到 sys.path 中,使得 Python 能够在该路径下查找模块。

  • 最终,"/workspace/app" 被添加到 sys.path 中。

原理

在 Python 中,模块的导入是基于 sys.path 中的路径进行查找的。默认情况下,sys.path 包含以下路径:

  1. 当前脚本所在的目录。

  2. Python 标准库路径。

  3. 环境变量 PYTHONPATH 中指定的路径。

如果 init_db.py 需要导入 app 模块,而 app 模块位于项目根目录(/workspace/app),则需要将项目根目录添加到 sys.path 中,否则 Python 会报错 ModuleNotFoundError: No module named 'app'