我有一个类似于下面的目录结构

meta_project
    project1
        __init__.py
        lib
            module.py
            __init__.py
    notebook_folder
        notebook.jpynb

当在notebook.jpynb中工作时,如果我尝试使用相对导入来访问module.py中的函数function():

from ..project1.lib.module import function

我得到以下错误:

SystemError                               Traceback (most recent call last)
<ipython-input-7-6393744d93ab> in <module>()
----> 1 from ..project1.lib.module import function

SystemError: Parent module '' not loaded, cannot perform relative import

有什么方法可以使用相对导入让它工作吗?

注意,笔记本服务器是在meta_project目录级别上实例化的,因此它应该能够访问这些文件中的信息。

还要注意,至少在最初的计划中,project1并没有被认为是一个模块,因此没有__init__.py文件,它只是作为一个文件系统目录。如果问题的解决方案需要将其视为一个模块,并包含__init__.py文件(甚至是一个空白文件),这是可以的,但这样做还不足以解决问题。

我在机器之间共享这个目录,相对导入允许我在任何地方使用相同的代码,而且我经常使用笔记本电脑进行快速原型设计,所以涉及拼接绝对路径的建议不太可能有帮助。


编辑:这与Python 3中的相对导入不同,后者一般讨论Python 3中的相对导入,特别是从包目录中运行脚本。这与在jupyter笔记本中尝试调用另一个目录中的本地模块中的函数有关,该目录具有不同的一般和特定方面。


在这本笔记本中,我有一个和你几乎相同的例子,我想用DRY的方式来说明相邻模块的函数的用法。

我的解决方案是通过在笔记本中添加这样一个代码片段来告诉Python额外的模块导入路径:

import os
import sys
module_path = os.path.abspath(os.path.join('..'))
if module_path not in sys.path:
    sys.path.append(module_path)

这允许你从模块层次结构中导入所需的函数:

from project1.lib.module import function
# use the function normally
function(...)

注意,如果你还没有project1/和lib/文件夹,有必要将空的__init__.py文件添加到它们。


来这里寻找在notebook中工作时将代码抽象为子模块的最佳实践。我不确定是否有最佳做法。我一直在提议这个。

项目层次结构如下:

├── ipynb
│   ├── 20170609-Examine_Database_Requirements.ipynb
│   └── 20170609-Initial_Database_Connection.ipynb
└── lib
    ├── __init__.py
    └── postgres.py

从20170609-Initial_Database_Connection.ipynb:

    In [1]: cd ..

    In [2]: from lib.postgres import database_connection

这是因为默认情况下Jupyter Notebook可以解析cd命令。注意,这并没有使用Python Notebook魔法。它只是工作,没有预先%bash。

考虑到我在100次中有99次使用Project Jupyter Docker图像在Docker中工作,下面的修改是幂等的

    In [1]: cd /home/jovyan

    In [2]: from lib.postgres import database_connection

到目前为止,这个公认的答案最适合我。然而,我一直担心的是,可能会出现这样的情况:我可能会将notebook目录重构为子目录,需要更改每个notebook中的module_path。我决定在每个笔记本目录中添加一个python文件来导入所需的模块。

因此,具有如下的项目结构:

project
|__notebooks
   |__explore
      |__ notebook1.ipynb
      |__ notebook2.ipynb
      |__ project_path.py
   |__ explain
       |__notebook1.ipynb
       |__project_path.py
|__lib
   |__ __init__.py
   |__ module.py

我在每个notebook子目录(notebook /explore和notebook /explain)中添加了文件project_path.py。这个文件包含了相对导入的代码(来自@metakermit):

import sys
import os

module_path = os.path.abspath(os.path.join(os.pardir, os.pardir))
if module_path not in sys.path:
    sys.path.append(module_path)

这样,我只需要在project_path.py文件中进行相对导入,而不是在笔记本中。然后,notebook文件只需要在导入lib之前导入project_path即可。例如,在0.0-notebook.ipynb中:

import project_path
import lib

这里需要提醒的是,逆转进口是行不通的。这行不通:

import lib
import project_path

因此,在进口过程中必须小心。


我自己研究了这个主题,并阅读了答案,我建议使用path.py库,因为它提供了一个上下文管理器来更改当前的工作目录。

然后你会得到

import path
if path.Path('../lib').isdir():
    with path.Path('..'):
        import lib

不过,您可以省略isdir语句。

在这里,我将添加打印语句,以便于跟踪所发生的事情

import path
import pandas

print(path.Path.getcwd())
print(path.Path('../lib').isdir())
if path.Path('../lib').isdir():
    with path.Path('..'):
        print(path.Path.getcwd())
        import lib
        print('Success!')
print(path.Path.getcwd())

在这个例子中输出(lib在/home/jovyan/shared/notebooks/by-team/data-vis/demos/lib):

/home/jovyan/shared/notebooks/by-team/data-vis/demos/custom-chart
/home/jovyan/shared/notebooks/by-team/data-vis/demos
/home/jovyan/shared/notebooks/by-team/data-vis/demos/custom-chart

由于该解决方案使用上下文管理器,因此无论您的内核在单元格之前处于什么状态,无论导入库代码时会抛出什么异常,都可以保证返回到以前的工作目录。


我刚刚找到了一个漂亮的解决方案:

import sys; sys.path.insert(0, '..') # add parent folder path where lib folder is
import lib.store_load # store_load is a file on my library folder

你只需要文件的一些函数

from lib.store_load import your_function_name

如果python版本>= 3.3,则文件夹中不需要init.py文件


以下是我的观点:

import sys
# map the path where the module file is located. In my case it was the desktop
sys.path.append('/Users/John/Desktop')

# Either import the whole mapping module BUT then you have to use the .notation to map the classes like mapping.Shipping()

import mapping #mapping.py is the name of my module file

shipit = mapping.Shipment() #Shipment is the name of the class I need to use in the mapping module

# Or import the specific class from the mapping module
from mapping import Mapping

shipit = Shipment() #Now you don't have to use the .notation

我发现python-dotenv非常有效地解决了这个问题。您的项目结构最终会略有变化,但笔记本中的代码更简单一些,并且在不同笔记本中保持一致。

对于您的项目,做一点安装。

pipenv install python-dotenv

然后,项目变更为:

├── .env (this can be empty)
├── ipynb
│   ├── 20170609-Examine_Database_Requirements.ipynb
│   └── 20170609-Initial_Database_Connection.ipynb
└── lib
    ├── __init__.py
    └── postgres.py

最后,您的导入更改为:

import os
import sys

from dotenv import find_dotenv


sys.path.append(os.path.dirname(find_dotenv()))

这个包的优点是你的笔记本可以有好几个目录。Python-dotenv将在父目录中找到最接近的一个并使用它。这种方法的+2是jupyter将在启动时从.env文件加载环境变量。双重打击。


所有其他答案都取决于在笔记本中添加代码(!)

在我看来,将特定的路径硬编码到笔记本代码中是一种糟糕的做法,或者依赖于位置,因为这使得以后很难重构代码。相反,我建议您在启动Jupyter笔记本服务器时将根项目文件夹添加到PYTHONPATH,或者直接从项目文件夹中添加,如下所示

env PYTHONPATH='pwd' jupyter notebook

或者如果你从其他地方启动它,像这样使用绝对路径

env PYTHONPATH=/Users/foo/bar/project/ jupyter notebook


对于那些不像我一样理解解决方案的人,您必须根据您的特定问题深入到所需的目录中。此错误的解决方案:

没有名为your_folder的模块

我工作用的笔记本在:

C: \ Users \ vojte威廉姆斯喝predicta EDA喝公寓\ EDA.ipynb。

我想导入这个笔记本:

C:\Users\vojte\projects\functions\ functions.ipynb

我不得不通过@metakermit修改上面的解决方案为这个解决方案:

import os
import sys
module_path = os.path.abspath(os.path.join('..\..\..'))
if module_path not in sys.path:
    sys.path.append(module_path)

from functions import functions as f

您可以检查sys。附加目录的路径。在这个例子中,这一行被追加了:

C: \ \ \ \用户vojte \ \项目


改进@joshua-cook使用cd的答案。,为了确保你没有重新运行单元格,并在使用run all without restart时搞乱你的目录,请使用以下代码:

if 'NOTEBOOK_INITIATED_FLAG' not in globals():
    NOTEBOOK_INITIATED_FLAG = True
    %cd ..
%pwd

NOTEBOOK_INITIATED_FLAG用作占位符,标记内核已经在运行,因此不需要更改目录。

这是一个超级复杂的样板文件,如果你想使用jupytext并从父文件夹运行你的.py文件:

import os
import sys

if 'NOTEBOOK_INITIATED_FLAG' not in globals():
    NOTEBOOK_INITIATED_FLAG = True
    
    try:
        # not in notebook
        module_path = os.path.join(os.path.dirname(__file__), os.pardir)
    except:
        # in notebook
        module_path = os.path.abspath(os.path.join('..'))
        %cd ..
        %pwd

    if module_path not in sys.path:
        sys.path.append(module_path)

我很生气,所以我写了一个包裹。

pip install package_root,然后你可以将package_root #导入你的PYTHONPATH。

这是通过查看调用堆栈来确定哪个文件导入了package_root,然后遍历目录以找到缺少__init__.py文件的第一个目录。

所以在实践中,你需要将__init__.py文件添加到你的笔记本目录中,就像让Python包工作一样。


如果你在VS Code中通过Jupyter扩展工作,我发现你现在可以在你的设置中设置这个路径。json文件。

{
    "jupyter.notebookFileRoot": "/path/to/your/module/root"
}

编辑:或者,更一般地将其设置在工作区根目录下:

{
    "jupyter.notebookFileRoot": "${workspaceFolder}"
}

下面是一个基于这个答案的通用解决方案,它既不需要指定父文件夹名称,也不需要更改当前工作目录。

只需用相对导入开始的父级的数量更新root_parent_level,并确保每个子包中都存在__init__.py。

if "PKG" not in globals(): # `PKG` is used just to avoid re-excuting the cell more than once
  root_parent_level = 2
  import importlib, sys, pathlib
  PKG = %pwd
  PKG = pathlib.Path(PKG)
  root = PKG
  full_pkg = f"{root.name}"
  for _ in range(root_parent_level):
    root = root.parent
    full_pkg = f"{root.name}.{full_pkg}"
    MODULE_PATH = f"{root}{pathlib.os.path.sep}__init__.py"
    MODULE_NAME = f"{root.name}"
    spec = importlib.util.spec_from_file_location(MODULE_NAME, MODULE_PATH)
    module = importlib.util.module_from_spec(spec)
    sys.modules[spec.name] = module 
    spec.loader.exec_module(module)
  __package__ = full_pkg