使用Python 3从Jupyter Notebook中相对导入的另一个目录中的模块导入本地函数


126

我有一个类似于以下内容的目录结构

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

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

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笔记本中工作有关,该笔记本试图调用另一个目录中具有不同常规和特定方面的本地模块中的函数。


1
__init__软件包目录中是否有文件?
Iron Fist

是的,在lib目录中。
mpacer

请不要提它在你的目录结构在你的问题
铁拳

看到您的第一条评论后,就进行编辑:)。感谢您抓住这一点。
mpacer

Answers:


172

此笔记本中,我有一个与您几乎相同的示例,在我想以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(...)

请注意,如果还没有空__init__.py文件,则必须将它们添加到project1 /lib /文件夹中。


6
这解决了能够使用或多或少的相对位置但仅间接地导入包的问题。我碰巧知道Matthias Bussonier(SE上的@matt)和Yuvi Panda(SE上的@yuvi)正在开发github.com/ipython/ipynb,它将更直接地解决此问题(例如,一旦打包后允许使用标准语法进行相对导入)导入)。我现在将接受您的答案,当他们的解决方案完全可供他人使用时,我可能会写一个关于如何使用它的答案,或者请其中一个这样做。
mpacer

感谢您指出空的init .py。我是python新手,无法导入我的类。我收到模块注释发现错误,添加空的init .py解决了该问题!
Pat Grady

5
的init .py文件不再需要在Python 3
CathyQian


25

在这里使用笔记本时,正在寻求将代码抽象到子模块的最佳实践。我不确定是否有最佳做法。我一直在提出这个建议。

这样的项目层次结构:

├── 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

考虑到我使用Project Jupyter Docker映像之一在Docker中工作的100次中有99次,以下修改幂等的

    In [1]: cd /home/jovyan

    In [2]: from lib.postgres import database_connection

谢谢。这种相对进口的限制真的很可怕。
迈克尔(Michael)

我也使用chdir而不是添加到路径,因为我既对从主存储库导入以及与其中的某些文件进行交互感兴趣。
TheGrimmScientist

可悲的是,我在python中做过的事情最多。但是,我找不到更好的解决方案。
TheGrimmScientist

简单幂等(允许同一小区多次运行及获得相同的结果):if os.path.isdir('../lib/'): os.chdir('../lib'); 或者,最好../lib/db/与您一起使用,postgres.py以免将chdir意外地移至包含另一个的更高目录lib
迈克尔,

1
我喜欢这种解决方案,直到我不小心执行了cd ..两次。
minhle_r7 '18

15

到目前为止,已接受的答案对我来说效果最好。但是,我一直担心的是,在某些情况下,我可能会将notebooks目录重构为子目录,从而需要module_path在每个笔记本中进行更改。我决定在每个笔记本目录中添加一个python文件,以导入所需的模块。

因此,具有以下项目结构:

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

project_path.py在每个笔记本子目录(notebooks/explorenotebooks/explain)中添加了文件。此文件包含相对导入的代码(来自@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文件中而不是在笔记本中进行相对导入即可。然后,笔记本文件仅需要在导入project_path之前导入lib。例如在0.0-notebook.ipynb

import project_path
import lib

需要注意的是,逆转进口将行不通。这不起作用:

import lib
import project_path

因此在进口期间必须小心。


3

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

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文件


3
我发现这很有帮助。我要补充一点,应该添加以下修改->if ".." not in sys.path: ... sys.path.insert(0,"..")
Yaakov Bressler,

2

我自己研究此主题并阅读答案,因此我建议使用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

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


这将无法与%autoreload结合使用,因为在重新加载时找不到模块路径
Johannes

1

这是我的2美分:

导入系统

映射模块文件所在的路径。就我而言,它是台式机

sys.path.append('/ Users / John / Desktop')

要么导入整个映射模块,要么然后使用.notation来映射诸如mapping.Shipping()的类。

导入映射#mapping.py是我的模块文件的名称

shipit = mapping.Shipment()#Shipment是我需要在映射模块中使用的类的名称

或从映射模块导入特定的类

从映射导入映射

shipit = Shipment()#现在,您不必使用.notation


0

我发现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()))

此软件包的+1是您的笔记本可以位于多个目录中。python-dotenv将在父目录中找到最接近的目录并使用它。此方法的+2是jupyter将在启动时从.env文件加载环境变量。双重打击。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.