Blog der Heimetli Software AG

Python-Script in Jupyter Notebook konvertieren

Jupyter-Notebooks sind praktisch wenn man die Zwischenschritte eines Python-Programms genauer untersuchen will. Deshalb wollte ich ein einfaches Script in Jupyter einlesen.

Es gibt zwar %load, aber das liest das ganze Script in eine einzige Zelle ein. Das ist nicht das was mir vorschwebte. Bei Google war auch nichts zu finden, also habe ich mich selber dahinter geklemmt.

.py nach .ipynb

Das Script ist Text, das Notebook ein JSON-File, also ebenfalls Text. Die Aufgabe ist demzufolge gut lösbar, wenn man einige Details beachtet.

Die wichtigste Frage: an welcher Stelle wird das Script in Zeilen aufgeteilt? Hier habe ich mich für eine Leerzeile entschieden. Der Code zwischen zwei Leerzeilen kommt in eine Zelle, der Block bis zur nächsten Leerzeile in die nächste.

Beim Script das ich analysieren wollte hat das ganz gut geklappt, aber da ich meinen Code gerne mit Leerzilen strukturiere klappt es damit nicht so recht. Es ist aber immer noch einfacher die Leerzeilen aus dem Code zu nehmen und im Notebook wieder einzusetzen als den Code stückweise in ein Notebook zu pasten...

Selbstverständlich könnte man auch Marker ins Script setzen und das Programm an diesen Stellen aufteilen, aber das Löschen der Leerzeilen fand ich einfacher. Wer eine absolut geniale Idee hat um dieses Problem zu lösen soll sich bitte bei mir melden ;-)

Denkbar ist auch die Kommentare im Script als Markdown-Zellen darzustellen. Das habe ich verworfen um das Programm so einfach wie möglich zu halten.

convert.py

Das Script ist eigentlich straight forward, wenn man vom ganzen Quoting absieht. Vielleicht könnte man es mit einer JSON-Library noch vereinfachen, aber für meine Zwecke reicht es völlig.

import sys

def cell( out, lines, id ):
    if id > 1:
        out.write( "," )

    out.write( "  {\n" )
    out.write( "   \"cell_type\": \"code\",\n" )
    out.write( "   \"execution_count\": null,\n" )
    out.write(f"   \"id\": \"{id}\",\n" )
    out.write( "   \"metadata\": {},\n" )
    out.write( "   \"outputs\": [],\n" )
    out.write( "   \"source\": [\n" )

    code = "\\n\",\n     \"".join(lines)
    out.write( f"    \"{code}\"\n" )

    out.write( "   ]\n" )
    out.write( "  }" )

def process_file( inputfile, outputfile ):
    with open(outputfile,"w") as out:
        out.write( "{\n" )
        out.write( " \"cells\": [\n" )

        id = 1
        lines = []
        with open(inputfile) as infile:
            for line in infile:
                line = line.rstrip()

                if line == "":
                    cell( out, lines, id )
                    lines = []
                    id   += 1
                else:
                    lines.append( line.replace("\\","\\\\").replace("\"","\\\"") )

        if len(lines) > 0:
            cell( out, lines, id )

        out.write( " ],\n" )
        out.write( " \"metadata\": {\n" )
        out.write( "  \"kernelspec\": {\n" )
        out.write( "   \"display_name\": \"Python 3 (ipykernel)\",\n" )
        out.write( "   \"language\": \"python\",\n" )
        out.write( "   \"name\": \"python3\"\n" )
        out.write( "  },\n" )
        out.write( "  \"language_info\": {\n" )
        out.write( "   \"codemirror_mode\": {\n" )
        out.write( "    \"name\": \"ipython\",\n" )
        out.write( "    \"version\": 3\n" )
        out.write( "   },\n" )
        out.write( "   \"file_extension\": \".py\",\n" )
        out.write( "   \"mimetype\": \"text/x-python\",\n" )
        out.write( "   \"name\": \"python\",\n" )
        out.write( "   \"nbconvert_exporter\": \"python\",\n" )
        out.write( "   \"pygments_lexer\": \"ipython3\",\n" )
        out.write( "   \"version\": \"3.9.7\"\n" )
        out.write( "  }\n" )
        out.write( " },\n" )
        out.write( " \"nbformat\": 4,\n" )
        out.write( " \"nbformat_minor\": 5\n" )
        out.write( "}\n" )


if __name__ == '__main__':
    if len(sys.argv) == 3:
        process_file( sys.argv[1], sys.argv[2] )
    else:
       print( f"usage: python3 {sys.argv[0]} inputfile outputfile" )

Aufruf

Das Programm erwartet den Namen des Scripts und den Namen des Outputfiles auf der Kommandozeile:

> python3 convert.py script.py script.ipynb

Die Filenamen übernimmt das Programm genau so wie sie angegeben werden. Sie können also beliebige Filetypen verarbeiten.