使用nbconvert执行Jupyter Notebook(包括内联Markdown)

Execute a Jupyter notebook including inline markdown with nbconvert

我有一个Jupyter笔记本,它在markdown单元格中包含python变量,如下所示:

代码单元:

1
x = 10

降价单元:

1
The value of x is {{x}}.

如果我在笔记本中使用shift-enter执行markdown单元,则IPython-notebook-extension Python Markdown允许我动态显示这些变量。

降价单元:

1
The value of x is 10.

我想以编程方式执行笔记本中的所有单元格,并使用以下方法将它们保存到新笔记本中:

1
2
3
4
5
6
7
8
9
import nbformat
from nbconvert.preprocessors import ExecutePreprocessor

with open('report.ipynb') as f:
    nb = nbformat.read(f, as_version=4)
        ep = ExecutePreprocessor(timeout=600, kernel_name='python3')
        ep.preprocess(nb, {})
with open('report_executed.ipynb', 'wt') as f:
    nbformat.write(nb, f)

这将执行代码单元,但不执行降价单元。 他们仍然看起来像这样:

1
The value of x is {{x}}.

我认为问题是笔记本电脑不受信任。 有没有办法告诉ExecutePreprocessor信任笔记本? 还有另一种方法可以以编程方式执行Markdown单元中包含python变量的笔记本吗?


ExecutePreprocessor只查看代码单元,因此您的markdown单元完全未受影响。如前所述,要进行降价处理,您需要Python Markdown预处理器。

不幸的是,Python Markdown预处理系统仅在实时笔记本中执行代码,它通过修改与渲染单元格有关的javascript来执行。修改将执行代码段的结果存储在单元元数据中。

PyMarkdownPreprocessor类(在pre_pymarkdown.py中)设计为与nbconvert一起使用,该nbconvert可用于在实时笔记本设置中首先呈现的笔记本上。它处理降价单元,用存储在元数据中的值替换{{}}模式。

但是,根据您的情况,您没有实时笔记本的元数据。我有一个类似的问题,我通过编写自己的执行预处理器解决了这个问题,该预处理器还包括处理减价单元的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
from nbconvert.preprocessors import ExecutePreprocessor, Preprocessor
import nbformat, nbconvert
from textwrap import dedent

class ExecuteCodeMarkdownPreprocessor(ExecutePreprocessor):

    def __init__(self, **kw):
        self.sections = {'default': True} # maps section ID to true or false
        self.EmptyCell = nbformat.v4.nbbase.new_raw_cell("")

        return super().__init__(**kw)

    def preprocess_cell(self, cell, resources, cell_index):
       """
        Executes a single code cell. See base.py for details.
        To execute all cells see :meth:`preprocess`.
       """

        if cell.cell_type not in ['code','markdown']:
            return cell, resources

        if cell.cell_type == 'code':
            # Do code stuff
            return self.preprocess_code_cell(cell, resources, cell_index)

        elif cell.cell_type == 'markdown':
            # Do markdown stuff
            return self.preprocess_markdown_cell(cell, resources, cell_index)
        else:
            # Don't do anything
            return cell, resources

    def preprocess_code_cell(self, cell, resources, cell_index):
        ''' Process code cell.
        '''
        outputs = self.run_cell(cell)
        cell.outputs = outputs

        if not self.allow_errors:
            for out in outputs:
                if out.output_type == 'error':
                    pattern = u"""\
                        An error occurred while executing the following cell:
                        ------------------
                        {cell.source}
                        ------------------
                        {out.ename}: {out.evalue}
                       """
                    msg = dedent(pattern).format(out=out, cell=cell)
                    raise nbconvert.preprocessors.execute.CellExecutionError(msg)

        return cell, resources

    def preprocess_markdown_cell(self, cell, resources, cell_index):
        # Find and execute snippets of code
        cell['metadata']['variables'] = {}
        for m in re.finditer("{{(.*?)}}", cell.source):
            # Execute code
            fakecell = nbformat.v4.nbbase.new_code_cell(m.group(1))
            fakecell, resources = self.preprocess_code_cell(fakecell, resources, cell_index)

            # Output found in cell.outputs
            # Put output in cell['metadata']['variables']
            for output in fakecell.outputs:
                html = self.convert_output_to_html(output)
                if html is not None:
                    cell['metadata']['variables'][fakecell.source] = html
                    break
        return cell, resources

    def convert_output_to_html(self, output):
        '''Convert IOpub output to HTML

        See https://github.com/ipython-contrib/IPython-notebook-extensions/blob/master/nbextensions/usability/python-markdown/main.js
        '''
        if output['output_type'] == 'error':
            text = '**' + output.ename + '**: ' + output.evalue;
            return text
        elif output.output_type == 'execute_result' or output.output_type == 'display_data':
            data = output.data
            if 'text/latex' in data:
                html = data['text/latex']
                return html
            elif 'image/svg+xml' in data:
                # Not supported
                #var svg = ul['image/svg+xml'];
                #/* embed SVG in an <img> tag, still get eaten by sanitizer... */
                #svg = btoa(svg);
                #html = '<img src="data:image/svg+xml;base64,' + svg + '"/>';
                return None
            elif 'image/jpeg' in data:
                jpeg = data['image/jpeg']
                html = '<img src="data:image/jpeg;base64,' + jpeg + '"/>'
                return html
            elif 'image/png' in data:
                png = data['image/png']
                html = '<img src="data:image/png;base64,' + png + '"/>'
                return html
            elif 'text/markdown' in data:
                text = data['text/markdown']
                return text
            elif 'text/html' in data:
                html = data['text/html']
                return html
            elif 'text/plain' in data:
                text = data['text/plain']
                # Strip <p>
 and
</p> tags
                # Strip quotes
                # html.match(/<p>
([\s\S]*?)<\/p>/)[1]
                text = re.sub(r'<p>
([\s\S]*?)<\/p>', r'\1', text)
                text = re.sub(r"'([\s\S]*?)'",r'\1', text)
                return text
            else:
            # Some tag we don't support
                return None
        else:
            return None

然后,您可以使用类似于您发布的代码的逻辑处理笔记本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import nbformat
from nbconvert.preprocessors import ExecutePreprocessor
import ExecuteCodeMarkdownPreprocessor # from wherever you put it
import PyMarkdownPreprocessor # from pre_pymarkdown.py

with open('report.ipynb') as f:
    nb = nbformat.read(f, as_version=4)
    ep = ExecuteCodeMarkdownPreprocessor(timeout=600, kernel_name='python3')
    ep.preprocess(nb, {})
    pymk = PyMarkdownPreprocessor()
    pymk.preprocess(nb, {})

with open('report_executed.ipynb', 'wt') as f:
    nbformat.write(nb, f)

请注意,通过包含Python Markdown预处理,您生成的笔记本文件将不再在markdown单元格中使用{{}}语法-markdown将具有静态内容。如果最终笔记本的收件人更改了代码并再次执行,则降价不会更新。但是,如果要导出为其他格式(例如HTML),则需要用静态内容替换{{}}语法。