如何将Jupyter(IPython)笔记本会话保存或存储以供以后使用

147

假设我正在Jupyter/Ipython笔记本中进行大量耗时计算的数据分析。然后,由于某种原因,我必须关闭Jupyter本地服务器,但是我希望稍后返回继续进行分析,而不必再次进行所有耗时的计算。


我希望您能够将整个Jupyter会话(所有pandas数据框,np.arrays,变量等)进行pickle或存储,以便我可以安全地关闭服务器,并确信我可以回到之前完全相同的会话中。 这是否甚至在技术上可行? 我是否忽略了内置功能?
编辑:根据this的答案,有一个%storemagic,应该是“轻量级pickle”。然而,您必须手动存储变量,如下所示:
#在ipython/nb会话中
foo = "一个虚拟字符串"
%store foo
关闭会话,重新启动内核
%store -r foo # r用于刷新
print(foo) # "一个虚拟字符串"
这与我想要的相当接近,但需要手动操作,并且无法区分不同的会话,因此不太实用。

2
有进展吗?我只注意到Spyder IDE中有一个工作区,可以将变量保存为*.mat文件。但不确定是否可以移植到Jupyter Notebook中。 - ZK Zhao
你考虑过 https://pypi.python.org/pypi/dill 吗?"dill 还提供了以下功能:- 保存和加载 Python 解释器会话"。不过这只是 Python,不确定 IPython 或内核还涉及什么。 - piccolbo
4个回答

109
我认为Dillpip install dill)很好地回答了你的问题。
使用dill.dump_session来保存一个Notebook会话:
import dill
dill.dump_session('notebook_env.db')

使用dill.load_session来恢复一个Notebook会话:
import dill
dill.load_session('notebook_env.db')

来源

4
如果有发电机存在时,该系统会出现故障(想想也觉得有道理),但看起来这已经是我们能期望的最好结果了! - redacted
2
对我来说非常有效。需要记住的几件事:首先,如果您有挂起的pyodbc连接对象,则需要关闭它们,然后将它们全部设置为None,否则会出现“TypeError:无法pickle pyodbc.Connection对象”错误。其次,笔记本电脑状态不包括由代码生成的图形,因此您需要重新运行单元格以将这些内容带回。 - Michael Szczepaniak
但它不起作用,我在另一台机器上使用了保存的文件。 - Jaya A
安装了dill。我需要从命令行导入dill.dump_session('notebook_env.db')吗? - cheznead
3
不,你需要在运行Jupyter notebook时完成这个操作。dump_session和load_session都应该通过notebook来进行。你的load_session可以放在notebook的开头,而dump_session则可以放在notebook的最后。 - BoreBoar
我不建议使用这种方式。一旦您转储了会话,您将无法恢复。即使您卸载了dill库。基本上,避免使用这个库。 - Orhan Solak

30

我更愿意评论而不是将此作为真正的答案提供,但我需要更多的声望才能进行评论。

您可以以系统化的方式存储大多数类似数据的变量。 我通常会将所有数据框,数组等存储在pandas.HDFStore中。 在笔记本的开头,声明:

backup = pd.HDFStore('backup.h5')

然后将你生成的任何新变量存储起来

backup['var1'] = var1

最后,可能做一件好事

backup.close()

在关闭服务器之前。下次您想继续使用笔记本电脑时:

backup = pd.HDFStore('backup.h5')
var1 = backup['var1']

说实话,我也更喜欢IPython笔记本中的内置功能。但是你无法通过这种方式保存所有内容(例如对象、连接),并且在有太多样板代码时难以保持笔记本的组织性。


6
这是一个非常有趣的解决方法,但我真的能感受到维护这样的系统所伴随的痛苦。不过还是谢谢你的提示 :) - redacted
这是一个不错的解决方法。只是需要说明一下,这个解决方案可能需要安装“tables”模块才能创建备份文件。 - Meet

20
这个问题与如何在IPython笔记本中缓存?有关。
为了保存单独单元格的结果,缓存魔法非常有用。
%%cache longcalc.pkl var1 var2 var3
var1 = longcalculation()
....

当重新运行笔记本时,此单元格的内容将从缓存中加载。
这并不完全回答您的问题,但可能足以迅速恢复所有冗长计算的结果。结合在笔记本顶部点击“运行全部”按钮,对我来说是可行的解决方案。
缓存魔法无法保存整个笔记本的状态 (目前)。据我所知,还没有其他系统可以恢复“笔记本”。这需要保存Python内核的所有历史记录。加载笔记本并连接到内核后,应加载这些信息。

0

编辑

自从首次发布这个答案后,我制作了一个pypi包https://pypi.org/project/jupyter-save-load-vars。可以通过以下方式进行安装

pip install jupyter-save-load-vars

由于dill.dump_session在无法pickle化的对象上失败,并且显然无法配置为简单地忽略这些对象,因此我编写了一对静态函数savevars(file)loadvars(),它们位于this ne1 library中,用于我们的神经形态工程jupyter笔记本类芯片练习。

这种方法基于有用的帖子Get locals from calling namespace in Python

使用示例:

from ne1 import savevars,loadvars
a=1
b=[2,3]
c='string'
o=(i for i in []) # make generator that cannot be pickled

savevars('testvars')
del a,b,c
loadvars('testvars')
print(a)
print(b)
print(c)

输出:

[INFO]: 2023-10-02 08:28:32,878 - NE1 - saved to testvars.dill variables [ a b c ] (File "/home/ne1/CoACH-labs/ne1.py", line 132, in savevars)
[WARNING]: 2023-10-02 08:28:32,879 - NE1 - could not pickle: ['o'] (File "/home/ne1/CoACH-labs/ne1.py", line 134, in savevars)
[INFO]: 2023-10-02 08:28:32,881 - NE1 - from testvars.dill loaded variables ['a', 'b', 'c'] (File "/home/ne1/CoACH-labs/ne1.py", line 158, in loadvars)
1
[2, 3]
string

为了完整地使用下面的代码,我还提供了一个自定义的日志记录器,用于格式化日志消息。如果你不想要这个日志输出,可以将log.XXX(替换为print(

savevars(filename)函数:

def savevars(filename):
    """
    saves all local variables to a file with dill
    
    :param filename: the name of the file. The suffix .dill is added if there is not a suffix already.
    """
    if filename is None:
        log.error('you must supply a filename')
        return
    from pathlib import Path
    p=Path(filename)
    if p.suffix=='': # if suffix is missing add .dill
        p = p.parent / (p.name + _DILL)

    import inspect,dill
    locals=None
    frame = inspect.currentframe().f_back
    try:
        locals = frame.f_locals
    finally:
        del frame
    if locals is None: return

    data={}
    could_not_pickle=[]

    from types import ModuleType
    s=f'saved to {p} variables [ '
    for k,v in locals.items():
        # don't try to pickle any pyplane objects
        if k.startswith('_') or k=='tmp' or k=='In' or k=='Out' or hasattr(v, '__call__') \
            or isinstance(v,ModuleType) or isinstance(v,logging.Logger): 
            continue
        try:
            if not dill.pickles(v):
                could_not_pickle.append(k)
                continue
        except:
            could_not_pickle.append(k)
            continue
        s=s+k+' '
        data[k]=v
    s=s+']'
    try:
       with open(p,'wb') as f:
            try:
                dill.dump(data,f)
                log.info(f'{s}')
                if len(could_not_pickle)>0:
                    log.warning(f'could not pickle: {could_not_pickle}')
            except TypeError as e:
                log.error(f'\n Error: {e}')
    except Exception as e:
        log.error(f'could not save data to {p}')

loadvars() 函数

def loadvars(filename):
    """ Loads variables from file into the current workspace
    
    :param filename: the dill file to load from, e.g. lab1. The suffix .dill is added automatically unless there is already a suffix.

    This function loads the variables found in filename into the parent workspace.
    """
    import dill
    from pathlib import Path
    p=Path(filename)
    if p.suffix=='': # if suffix is missing add .dill
        p = p.parent / (p.name + _DILL)
    if not p.exists:
        log.error(f'{p} does not exist')
        return
    try:
        with open(p,'rb') as f:
            data=dill.load(f)
            log.info(f'from {p} loaded variables {list(data.keys())}')
            import inspect
            try:
                frame = inspect.currentframe().f_back # get the workspace frame (jupyter workspace frame)
                locals = frame.f_locals # get its local variable dict
                for k in data:
                    try:
                        locals[k] = data[k] # set a value in it
                    except Exception as e:
                        log.error(f'could not set variable {k}')
            finally:
                del frame
    except Exception as e:
        log.error(f'could not load; got {e}')

自定义的日志记录器,具有格式化和代码行超链接,在PyCharm中可正常工作。
import logging
# general logger. Produces nice output format with live hyperlinks for pycharm users
# to use it, just call log=get_logger() at the top of your python file
# all these loggers share the same logger name 'Control_Toolkit'

_LOGGING_LEVEL = logging.DEBUG # usually INFO is good

class CustomFormatter(logging.Formatter):
    """Logging Formatter to add colors and count warning / errors"""
    # see https://dev59.com/wnRC5IYBdhLWcg3wMd5S#7995762

    # \x1b[ (ESC[) is the CSI introductory sequence for ANSI https://en.wikipedia.org/wiki/ANSI_escape_code
    # The control sequence CSI n m, named Select Graphic Rendition (SGR), sets display attributes.
    grey = "\x1b[2;37m" # 2 faint, 37 gray
    yellow = "\x1b[33;21m"
    cyan = "\x1b[0;36m" # 0 normal 36 cyan
    green = "\x1b[31;21m" # dark green
    red = "\x1b[31;21m" # bold red
    bold_red = "\x1b[31;1m"
    light_blue = "\x1b[1;36m"
    blue = "\x1b[1;34m"
    reset = "\x1b[0m"
    # File "{file}", line {max(line, 1)}'.replace("\\", "/")
    format = '[%(levelname)s]: %(asctime)s - %(name)s - %(message)s (File "%(pathname)s", line %(lineno)d, in %(funcName)s)'

    FORMATS = {
        logging.DEBUG: grey + format + reset,
        logging.INFO: cyan + format + reset,
        logging.WARNING: red + format + reset,
        logging.ERROR: bold_red + format + reset,
        logging.CRITICAL: bold_red + format + reset
    }

    def format(self, record):
        log_fmt = self.FORMATS.get(record.levelno)
        formatter = logging.Formatter(log_fmt)
        return formatter.format(record).replace("\\", "/") #replace \ with / for pycharm links


def get_logger():
    """ Use get_logger to define a logger with useful color output and info and warning turned on according to the global LOGGING_LEVEL.

    :returns: the logger.
    """
    # logging.basicConfig(stream=sys.stdout, level=logging.INFO)
    logger = logging.getLogger('NE1') # tobi changed so all have same name so we can uniformly affect all of them
    logger.setLevel(_LOGGING_LEVEL)
    # create console handler if this logger does not have handler yet
    if len(logger.handlers)==0:
        ch = logging.StreamHandler()
        ch.setFormatter(CustomFormatter())
        logger.addHandler(ch)
    return logger

log=get_logger()

请注意,此loadvars()函数会悄悄地覆盖任何现有变量。有关更新版本,请访问https://code.ini.uzh.ch/CoACH/CoACH-labs/-/blob/master/ne1.py。 - undefined
忽略之前的版本,而是查看 https://github.com/tobidelbruck/jupyter-save-load-vars/tree/main,这是一个现在的pypi包 https://pypi.org/project/jupyter-save-load-vars/。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接