在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,加入系统路径。

步骤:

  1. 将所涉及的文件夹转化为包:在文件夹下新建__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
  1. 添加包的根目录到系统路径:将所需最顶级的目录(此处为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
  1. 后续:若仍有报错,可以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))
posted @ 2023-02-24 20:01  X1OO  阅读(749)  评论(0)    收藏  举报