转换为(而非从)IPython Notebook格式

75
IPython Notebook自带nbconvert,可以将笔记本导出到其他格式。但是如何在相反的方向上转换文本呢?我问这个问题是因为我已经有了材料和一个好的工作流程,但我想利用Notebook的交互环境。
可能的解决方案:通过导入.py文件可以创建笔记本,并且文档说明当nbconvert将笔记本导出为python脚本时,它会在注释中嵌入指令,可以用于重新创建笔记本。但是该信息附带有a disclaimer的免责声明,关于接受的格式没有任何记录,我找不到任何记录。(奇怪的是,在描述笔记本的JSON格式部分中显示了一个示例)。有人可以提供更多信息或更好的替代方案吗?

编辑(2016年3月1日): 接受的答案不再适用,因为某种原因,笔记本API版本4不支持此输入格式。 我已经添加了自我回答,展示如何使用当前(v4)API导入笔记本。(我不会取消接受当前答案,因为它在当时解决了我的问题,并指引我在自我回答中使用的资源。)

10个回答

45

由于被接受的答案中的代码已经失效,我添加了这个自问自答,展示如何使用当前版本(v4)的API将其导入笔记本。

输入格式

IPython Notebook API的2和3版本可以通过特殊的结构化注释导入Python脚本,并按需要将其分解为单元格。以下是一个示例输入文件(原始文档在此)。前两行是可选项并且会被忽略。(实际上,读者将会忽略文件中任何位置的 coding:<nbformat> 行)。

# -*- coding: utf-8 -*-
# <nbformat>3.0</nbformat>

# <markdowncell>

# The simplest notebook. Markdown cells are embedded in comments, 
# so the file is a valid `python` script. 
# Be sure to **leave a space** after the comment character!

# <codecell>

print("Hello, IPython")

# <rawcell>

# Raw cell contents are not formatted as markdown

(该API还接受过时指令<htmlcell><headingcell level=...>,它们会立即转换为其他类型。)

如何导入

由于某些原因,Notebook API的第4版不支持此格式。但这仍然是一种好的格式,因此值得通过导入到第3版并升级来支持它。原则上只需两行代码以及输入/输出即可实现:

from IPython.nbformat import v3, v4

with open("input-file.py") as fpin:
    text = fpin.read()

nbook = v3.reads_py(text)
nbook = v4.upgrade(nbook)  # Upgrade v3 to v4

jsonform = v4.writes(nbook) + "\n"
with open("output-file.ipynb", "w") as fpout:
    fpout.write(jsonform)

但是不要太着急!事实上,笔记本API存在一个讨厌的bug:如果输入中的最后一个单元格是markdown单元格,v3.reads_py() 就会将其丢失。 最简单的解决方法是在末尾添加一个虚假的 <markdown> 单元格:bug 会删除它,每个人都很高兴。 因此,在将 text 传递给 v3.reads_py() 之前,请��行以下操作:

text += """
# <markdowncell>

# If you can read this, reads_py() is no longer broken! 
"""

3
好的。请查看我写的这个脚本,它可以与Spyder和PyCharm单元格标记一起使用,并提供了一些用于制作幻灯片的额外功能。附注:也许你想将此添加到你的编辑中(我认为你的意思是2016) :) - John Smith
使用Python 2.7.11 :: Anaconda 2.4.1 (x86_64),Jupyter 4.0.6和Notebook 4.1.0。你用的是什么? - John Smith
糟糕!我忘记调用 v4.writes() 生成 JSON 了。现在已经修复了,感谢你的发现! - alexis
太好了。我可以将你的代码整合到我的脚本中(并适当地给予信用)以实现完整性吗? - John Smith
太好了,等我有时间就会这样做。不需要太长时间。顺便问一下,像 #<codecell>#<markdowncell> 这样的标记有没有文档呢?通过网络搜索找不到有用的内容。 - John Smith
显示剩余5条评论

44

非常古老的问题,我知道。但是有一个jupytext(也可在pypi上获得)可以将ipynb转换为多种格式并进行还原。

当安装了jupytext之后,您可以使用以下命令:

$ jupytext --to notebook test.py
为了生成test.ipynbjupytext有很多有趣的功能,在使用笔记本时非常方便。
这是关于该主题的更近期的问题

1
好的,谢谢你的更新!我会去查看一下。 - alexis
2
@alexis,你能把这个答案添加到你的问题文本中吗?因为它在2019年秋季确实有效,并且绝对没有任何麻烦。 - Jantar88

43

以下内容适用于IPython 3,但不适用于IPython 4。

IPython API提供了读写笔记本文件的功能。您应该使用此API而不是直接创建JSON。例如,以下代码片段将脚本test.py转换为笔记本test.ipynb

import IPython.nbformat.current as nbf
nb = nbf.read(open('test.py', 'r'), 'py')
nbf.write(nb, open('test.ipynb', 'w'), 'ipynb')

关于 .py 文件的格式,nbf.read 可以理解为最好是查看解析器类 IPython.nbformat.v3.nbpy.PyReader。可以在此处找到代码(它不是很大):

https://github.com/ipython/ipython/blob/master/jupyter_nbformat/v3/nbpy.py

编辑:这个答案最初是针对 IPython 3 写的。我不知道如何在 IPython 4 中正确地做到这一点。这里是上面链接的更新版本,指向 IPython 3.2.1 版本的 nbpy.py

https://github.com/ipython/ipython/blob/rel-3.2.1/IPython/nbformat/v3/nbpy.py

基本上,您可以使用特殊注释,例如 # <codecell># <markdowncell> 来分隔各个单元格。请查看 PyReader.to_notebook 中的 line.startswith 语句,获取完整列表。


1
谢谢!那个我在问题中链接的 .py 示例文件和这些指令似乎已经足够了。为了完整起见,当前的指令包括 <nbformat>(3.0)、<codecell><htmlcell><markdowncell><rawcell><headincell level=N>。(我忽略了已弃用的 <plaintextcell>。) - alexis
3
至少在我的安装中,这个 import 语句会出现 UserWarning: IPython.nbformat.current is deprecated. 的警告信息。 - LondonRob
@Alex,这对你有用吗?据我所知,Python的导入/导出格式已经从笔记本格式v.4中删除了——“codecell”和“markdowncell”标记甚至在源代码中都没有出现。我不得不使用v3笔记本阅读器来读取Python,然后保存为v4。 - alexis
@alexis 是的,我认为你是对的,我在v4中也看不到任何那些代码。我不得不将它转换为v3笔记本,打开它并让IPython像你一样将其转换为v4。这绝不是一个干净的解决方案,但似乎可以工作。 - Alex
3
@Alex,我已经添加了一个自我答案,展示如何导入笔记本并立即保存为v4格式。 (还有如何避免一个恶性bug...) - alexis
显示剩余5条评论

11

Python代码示例:如何构建IPython Notebook V4:

# -*- coding: utf-8 -*-
import os
from base64 import encodestring

from IPython.nbformat.v4.nbbase import (
    new_code_cell, new_markdown_cell, new_notebook,
    new_output, new_raw_cell
)

# some random base64-encoded *text*
png = encodestring(os.urandom(5)).decode('ascii')
jpeg = encodestring(os.urandom(6)).decode('ascii')

cells = []
cells.append(new_markdown_cell(
    source='Some NumPy Examples',
))


cells.append(new_code_cell(
    source='import numpy',
    execution_count=1,
))

cells.append(new_markdown_cell(
    source='A random array',
))

cells.append(new_raw_cell(
    source='A random array',
))

cells.append(new_markdown_cell(
    source=u'## My Heading',
))

cells.append(new_code_cell(
    source='a = numpy.random.rand(100)',
    execution_count=2,
))
cells.append(new_code_cell(
    source='a = 10\nb = 5\n',
    execution_count=3,
))
cells.append(new_code_cell(
    source='a = 10\nb = 5',
    execution_count=4,
))

cells.append(new_code_cell(
    source=u'print "ünîcødé"',
    execution_count=3,
    outputs=[new_output(
        output_type=u'execute_result',
        data={
            'text/plain': u'<array a>',
            'text/html': u'The HTML rep',
            'text/latex': u'$a$',
            'image/png': png,
            'image/jpeg': jpeg,
            'image/svg+xml': u'<svg>',
            'application/json': {
                'key': 'value'
            },
            'application/javascript': u'var i=0;'
        },
        execution_count=3
    ),new_output(
        output_type=u'display_data',
        data={
            'text/plain': u'<array a>',
            'text/html': u'The HTML rep',
            'text/latex': u'$a$',
            'image/png': png,
            'image/jpeg': jpeg,
            'image/svg+xml': u'<svg>',
            'application/json': {
                'key': 'value'
            },
            'application/javascript': u'var i=0;'
        },
    ),new_output(
        output_type=u'error',
        ename=u'NameError',
        evalue=u'NameError was here',
        traceback=[u'frame 0', u'frame 1', u'frame 2']
    ),new_output(
        output_type=u'stream',
        text='foo\rbar\r\n'
    ),new_output(
        output_type=u'stream',
        name='stderr',
        text='\rfoo\rbar\n'
    )]
))

nb0 = new_notebook(cells=cells,
    metadata={
        'language': 'python',
    }
)

import IPython.nbformat as nbf
import codecs
f = codecs.open('test.ipynb', encoding='utf-8', mode='w')
nbf.write(nb0, f, 4)
f.close()

感谢您的努力。这并不完全符合问题(导入文件)的要求,但对于其他有志于笔记本黑客的人可能会有用。 - alexis

10

感谢您的报告。您是否考虑过标题、原始单元格、应保留为Python注释的注释以及其他笔记本格式特性?在您的Github页面中没有提到它们。此外,最好添加特殊处理emacs指令等(# -*- coding: utf-8 -*-),因为您自己使用它们。最后,请指示您是使用jupyter api还是自己编写json生成代码。 - alexis
嗨@alexis。我刚刚更新了我的GitHub存储库上的自述文件,并包含了指令。希望这能回答你的第一个问题。至于你的最后一个问题,我正在自己编写JSON生成代码。 - remykarem

7

在Volodimir Kopey的示例基础上,我编写了一个简单的脚本,将从.ipynb导出的.py文件转换回V4 .ipynb格式。

当我想要逐个单元格运行已从Notebook导出的.py文件时,在编辑器中编辑了该文件,并使用此脚本将其转换回Notebook格式。

该脚本仅处理代码单元格。反正导出的.py文件也没有太多其他内容。

import nbformat
from nbformat.v4 import new_code_cell,new_notebook

import codecs

sourceFile = "changeMe.py"     # <<<< change
destFile = "changeMe.ipynb"    # <<<< change


def parsePy(fn):
    """ Generator that parses a .py file exported from a IPython notebook and
extracts code cells (whatever is between occurrences of "In[*]:").
Returns a string containing one or more lines
"""
    with open(fn,"r") as f:
        lines = []
        for l in f:
            l1 = l.strip()
            if l1.startswith('# In[') and l1.endswith(']:') and lines:
                yield "".join(lines)
                lines = []
                continue
            lines.append(l)
        if lines:
            yield "".join(lines)

# Create the code cells by parsing the file in input
cells = []
for c in parsePy(sourceFile):
    cells.append(new_code_cell(source=c))

# This creates a V4 Notebook with the code cells extracted above
nb0 = new_notebook(cells=cells,
                   metadata={'language': 'python',})

with codecs.open(destFile, encoding='utf-8', mode='w') as f:
    nbformat.write(nb0, f, 4)

不保证一定有效,但对我来说有效


谢谢!我已经使用#<...cell>格式拥有了整个工具链,但是你的解决方案可能对其他人有用——特别是因为Notebook导出时会生成你的格式。你能否更好地记录它(parsePy返回行的列表?每个单元格一个?如何区分标记和代码单元格?)并在答案文本中添加一个示例输入文件? - alexis

4

我自由地拿了P.Toccateli和alexis的代码,进行修改,使其也能在pycharm和spyder中像单元标记一样使用,并发布到github上。


谢谢。这对于任何使用这些格式的人都应该很有用。 - alexis

4
我为vscode写了一个扩展程序,可能会有所帮助。它可以将Python文件转换为IPython笔记本。目前还处在早期阶段,如果出现任何错误,请随时提交问题。 Jupyter Notebook Converter

1

对 @p-toccaceli 的回答进行了一些改进。 现在,它还可以恢复markdown单元格。此外,它会为每个单元格修剪空的悬挂行。

    import nbformat
    from nbformat.v4 import new_code_cell,new_markdown_cell,new_notebook

    import codecs

    sourceFile = "changeMe.py"     # <<<< change
    destFile = "changeMe.ipynb"    # <<<< change


    def parsePy(fn):
        """ Generator that parses a .py file exported from a IPython notebook and
    extracts code cells (whatever is between occurrences of "In[*]:").
    Returns a string containing one or more lines
    """
        with open(fn,"r") as f:
            lines = []
            for l in f:
                l1 = l.strip()
                if l1.startswith('# In[') and l1.endswith(']:') and lines:
                    yield ("".join(lines).strip(), 0)
                    lines = []
                    continue
                elif l1.startswith('# ') and l1[2:].startswith('#') and lines:
                    yield ("".join(lines).strip(), 0)

                    yield (l1[2:].strip(), 1)
                    lines = []
                    continue
                lines.append(l)
            if lines:
                yield ("".join(lines).strip(), 0)

    # Create the code cells by parsing the file in input
    cells = []
    for c, code in parsePy(sourceFile):
        if len(c) == 0:
            continue
        if code == 0:
            cells.append(new_code_cell(source=c))
        elif code == 1:
            cells.append(new_markdown_cell(source=c))

    # This creates a V4 Notebook with the code cells extracted above
    nb0 = new_notebook(cells=cells,
                       metadata={'language': 'python',})

    with codecs.open(destFile, encoding='utf-8', mode='w') as f:
        nbformat.write(nb0, f, 4)

2
请注意,这个问题已经有一个被接受的答案了。请[编辑]您的答案,确保它能够改进已经存在于此问题中的其他答案。 虽然它可能回答了问题,但仅仅添加一些代码并不能帮助OP或未来的社区成员理解问题或解决方案。 - hongsy

0

你可以使用来自https://github.com/sklam/py2nb的py2nb脚本。

你需要为你的*.py文件使用特定的语法,但它非常简单易用(请查看“samples”文件夹中的示例)。


2
这种临时解决方案(采用临时格式和未知的错误和限制)相比于笔记本本身支持的格式,有什么好处呢? - alexis

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