python项目路径 no module named

遇到一个package中的代码无法导入另一个包中代码的问题

假设项目文件结构如下

some_directory/
├── gmip/
│   └── glir.py  
│   └── __init__.py (python3.3以上不是必要的了)
└── notebooks/
    └── my_notebook.ipynb

想在notebook.ipynb中导入 glir.py 中的代码 form gmip import glir,会报错 no module named 'gmip'

一般来说,有如下四种解决方法

1、修改sys.path(最简单)
import sys
import os

#获取 some_directory 的路径

some_directory = os.path.abspath(os.path.join(os.getcwd(), ".."))

if some_directory not in sys.path:
    sys.path.insert(0, some_directory)

# 注意这里不要添加 gmip 的绝对路径到 sys.path
# 否则Python 会尝试将 gmip 当作顶层模块进行导入,而不是包的一部分。  即看做路径的根目录

# sys.path.append(os.path.abspath(os.path.join(os.getcwd(), "..", "gmip")))

#导入的路径是gmip的路径的话 python解释器找不到gmip包,但是可以直接导入glir

导入的路径是 ./some_directory/gmip 的话虽然可以使用import glir导入相应代码,但这会让代码结构不清晰,因此不推荐。

添加错了也可以移除

# 移除错误的路径
if './mia/gaussian_mip/gmip' in sys.path:
    sys.path.remove('./mia/gaussian_mip/gmip')

需要注意的是,os.getcwd() 获取的是 Python 进程的当前工作目录,这可能与 notebook.ipynb 文件所在的目录不同,尤其是在 notebook.ipynb 中使用 os.chdir() 更改了工作目录的情况下。

因此更推荐使用如下方案

import os
import sys
# 获取当前脚本的路径
script_path = os.path.abspath(__file__)

# 获取脚本所在目录的路径
script_dir = os.path.dirname(script_path)

# 获取 some_directory 的路径 (假设 gmip 是脚本所在目录的同级目录)
some_directory = os.path.dirname(script_dir)

# 将 some_directory 添加到 sys.path
if some_directory not in sys.path:
    sys.path.insert(0, some_directory)


#  or


from IPython.display import display
from IPython.utils import capture

with capture.capture_output() as captured:
    !pwd

notebook_dir = captured.stdout.strip()
some_directory = os.path.dirname(notebook_dir)  # 如果 gmip 是 notebooks 的同级目录

if some_directory not in sys.path:
    sys.path.insert(0, some_directory)

此外,还可以使用 sys.path.append("..") 将some_directory/ 添加到sys.path

2、修改 PYTHONPATH 环境变量

设置环境变量 PYTHONPATH,让 Python 自动识别 gmip。可以在终端中运行以下命令:

export PYTHONPATH=/path/to/project:$PYTHONPATH

或者在 .ipynb 文件中动态设置环境变量:

import os
os.environ['PYTHONPATH'] = os.path.abspath(os.path.join(os.getcwd(), ".."))

但是这样做每次重启所设置的变量就会消失,想要永久生效的话就要修改 ~/bashrc (不推荐)

3、安装 gmip 作为包 (推荐)

在some_directory/文件夹中创建setup.py 文件。

setup.py 文件是 Python 项目的构建配置文件,它告诉 pip 如何构建和安装你的包。

一个简单的 setup.py 文件示例如下

from setuptools import setup, find_packages

setup(
    name='yourname',
    version='0.1.0',
    packages=find_packages(),
    install_requires=[
        # 列出你的包的依赖项
        'numpy',
        'scipy',
        # ...
    ],
    # 其他元数据
    author='Your Name',
    author_email='[email protected]',
    description='A brief description of your package',
    license='MIT',
    # ...
)

创建 setup.py 后,你就可以使用 pip install -e ./yourname 命令安装你的包了。命令中 ./yourname 来自于setup.py中的name变量,gmip包用find_packages()函数自动发现。

4、更改当前工作目录

更改 mynotebook 文件的工作目录到some_directory

import os

# 更改工作目录到项目根目录
os.chdir("..")  
print("Current directory:", os.getcwd())  # 检查当前目录

对于__init__.py:

__init__.py 文件的作用是将一个目录标记为 Python 包。

当 Python 解释器看到一个目录中包含 __init__.py 文件时,它会将该目录视为一个包,并允许你使用 import 语句导入其中的模块。

__init__.py 文件可以是空的,但它也可以包含一些初始化代码,例如导入子模块或定义包级别的变量。

哪些情况不需要 __init__.py?

  • 命名空间包: 如果你正在创建一个命名空间包(namespace package),它旨在跨多个目录组织代码,那么就不需要 __init__.py 文件。命名空间包允许你将一个大的包拆分成更小的、更易于管理的部分,这些部分可以位于文件系统的不同位置。 这是 __init__.py 不再严格要求的主要原因,因为它简化了命名空间包的管理。

  • 隐式命名空间包 (Python 3.3+): 这是最常见的情况。 只要包目录下有其他 Python 文件 (例如你的 glir.py),Python 3.3+ 会自动将其视为一个命名空间包,即使没有 __init__.py 文件。

从 Python 3.3 开始,即使文件夹中没有 __init__.py 文件,Python 也可以将其识别为一个包。这是因为从 Python 3.3 开始,支持了一种叫做隐式命名空间包(PEP 420)的机制。

详细说明

Python 3.3 之前
  • 要让一个文件夹被识别为包,必须包含一个 __init__.py 文件(即使是空文件也可以)。
  • 没有 __init__.py 的文件夹无法被识别为包,导入会失败。
Python 3.3 及之后
  • 文件夹中是否包含 __init__.py 不再强制要求。
  • 如果文件夹符合模块搜索路径规则(即在 sys.path 中),Python 会自动将其识别为一个命名空间包
什么是命名空间包
  • 命名空间包是指没有 __init__.py 的目录,Python 会自动将其内容合并到同名的包中。
  • 例如,如果 sys.path 中包含以下两个路径:
    • xxx/mia/gaussian_mip/gmip
    • xxx/another_project/gmip
  • 两个 gmip 目录的内容会被视为一个逻辑上的 gmip 包,导入时会合并。

是否需要 __init__.py

虽然不再强制要求,但建议仍然添加 __init__.py,原因包括:

  1. 向后兼容性:在某些旧的 Python 版本(如 2.x)中,仍然需要 __init__.py
  2. 明确包结构:添加 __init__.py 明确表示这是一个包,便于代码阅读和管理。
  3. 防止路径冲突:命名空间包可能导致路径冲突,而 __init__.py 能防止意外合并。

你可能感兴趣的:(python,前端,开发语言)