我有一个类似于下面的目录结构
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文件添加到它们。
我自己研究了这个主题,并阅读了答案,我建议使用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
由于该解决方案使用上下文管理器,因此无论您的内核在单元格之前处于什么状态,无论导入库代码时会抛出什么异常,都可以保证返回到以前的工作目录。
改进@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)
下面是一个基于这个答案的通用解决方案,它既不需要指定父文件夹名称,也不需要更改当前工作目录。
只需用相对导入开始的父级的数量更新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