在ipynb或py文件中import上层路径中的包
背景
在jupyter notebook中,需要import上层路径中的包,目录结构如下,需要从script/service/prj_query.ipynb中导入utils/conndb.py中的connectdb类。
D:
└─github_data
│
├─script
│ │
│ └─service
│ prj_query.ipynb
│ prj_query.py
│
└─utils
conndb.py
connectdb.ipynb
在prj_query.ipynb中引入时
from utils.conndb import connectdb
报错:
ModuleNotFoundError: No module named 'utils'
注:使用相对路径常常会遇到如下错误,也可以选用此方法解决:
attempted relative import beyond top-level package
解决方案:
原理: 在所涉及的文件夹下新建__init__.py,将每一级目录转化为可供引入的包,并将所需最顶级的目录作为包的根目录pkg_rootdir,加入系统路径。
步骤:
- 将所涉及的文件夹转化为包:在文件夹下新建__init__.py空文件(可通过 New->Text File 创建txt文件,再Rename)。
D:.
└─github_data
│ __init__.py
│
├─script
│ │ __init__.py
│ │
│ └─service
│ prj_query.ipynb
│ prj_query.py
│ __init__.py
│
└─utils
conndb.py
connectdb.ipynb
__init__.py
- 添加包的根目录到系统路径:将所需最顶级的目录(此处为D:\github_data)作为包的根目录pkg_rootdir,加入系统路径。
import os
import sys
cur_dir = os.getcwd()
pkg_rootdir = os.path.dirname(os.path.dirname(cur_dir))
# print(pkg_rootdir)
if pkg_rootdir not in sys.path:
sys.path.append(pkg_rootdir)
# print(sys.path)
from utils.conndb import connectdb
- 后续:若仍有报错,可以Restart the Kernel,或者重启jupyter notebook服务,刷新bash session后再次尝试。
Extra Problem: 上述代码在.py文件中编写,移植到.ipynb文件的过程中,若存在跨文件调用使用了pkg_rootdir的变量时,由于os.getcwd()会随着main函数的session而变动,会出现某些文件中pkg_rootdir路径与真正的ROOT_DIR不一致的bug。
在.py文件中可以通过__file__魔法变量找到文件的绝对路径,一劳永逸地解决跨文件调用时session变动的影响,但是在jupyter notebook的.ipynb文件中没有此魔法变量,会报错如下:
NameError: name '__file__' is not defined
原因: 由于notebook的.ipynb文件物理存储位置难以确定,因此不能给出文件的真实路径,包括使用os.path.realpath("."),依然会随着server的session变动,尤其是main线程所在的session。故而需要用kernel_id来匹配ipynb文件在可能的运行时server的session中的运行时id,从而给出一个相对正确的path。
其中,运行时当前.ipynb文件的路径难以确定的原因见:How to figure out the path of the current ipynb file from within IPython? · Issue #10123 · ipython/ipython · GitHub
解决方案: 使用包ipynbname (API: ipynbname · PyPI),并在文件头加入如下代码手动定义__file__的值,与.py文件保持一致,从而解决移植代码需要不断修改ROOT_DIR路径的问题。
if '__file__' not in globals():
# !pip install ipynbname # Remove comment symbols to solve the ModuleNotFoundError
import ipynbname
nb_path = ipynbname.path()
__file__ = str(nb_path)
推荐模板(Pycharm: File->Settings->Editor->File and Code Templates->Files->Python Script):
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Python 3.7
# @Time : ${DATE} ${TIME}
# @Author : 'Lou Zehua'
# @File : ${NAME}.py
import os
import sys
if '__file__' not in globals():
# !pip install ipynbname # Remove comment symbols to solve the ModuleNotFoundError
import ipynbname
nb_path = ipynbname.path()
__file__ = str(nb_path)
cur_dir = os.path.dirname(__file__)
pkg_rootdir = os.path.dirname(cur_dir) # os.path.dirname()向上一级,注意要对应工程root路径
if pkg_rootdir not in sys.path: # 解决ipynb引用上层路径中的模块时的ModuleNotFoundError问题
sys.path.append(pkg_rootdir)
print('-- Add root directory "{}" to system path.'.format(pkg_rootdir))

浙公网安备 33010602011771号