PythonTeX (intégrer la sortie)

Tout article contient un nombre calculé ailleurs — dans un script, un carnet, un tableur — puis recopié à la main dans le texte ; modifiez le script, et ce nombre devient discrètement faux. PythonTeX comble cet écart : le package exécute, pendant la composition, le Python écrit dans la source LaTeX, et compose ce qui en revient. Il est l'œuvre de Geoffrey M. Poore et, malgré son nom, pilote aussi Ruby, Julia, R, Octave, Bash, Rust, Perl et JavaScript. Cette page va de \usepackage{pythontex} à la famille de commandes \py, en passant par la compilation en trois étapes qui piège tout le monde une fois, et jusqu'au cas où il vaut mieux choisir autre chose.

Composer le code ou l'exécuter : PythonTeX face à listings et minted

listings et minted composent le code tel qu'il apparaît et n'en exécutent pas une ligne. PythonTeX diffère en ce qu'il exécute le code et compose la chaîne qui en revient. Écrivez \py{2**10} dans le texte : ce qui s'imprime n'est pas la suite de caractères 2**10 mais le résultat, 1024. Le chargement tient en une ligne, \usepackage{pythontex} ; pour l'exécution, il faut, à côté de la distribution TeX, Python lui-même ainsi que Pygments pour la coloration syntaxique.

document.tex
\documentclass{article}
\usepackage{pythontex}

\begin{document}
% executed, but nothing is typeset from this block itself
\begin{pycode}
from math import sqrt
radius = 2.5
area = 3.14159 * radius**2
\end{pycode}

A circle of radius \py{radius} has area \py{round(area, 2)}.

\[ 2^{10} = \py{2**10}, \qquad \sqrt{3^2+4^2} = \py{sqrt(3**2 + 4**2)} \]
\end{document}

Ce document compose « A circle of radius 2.5 has area 19.63. », suivi de 2¹⁰ = 1024 et √(3²+4²) = 5.0. Le point important : le nombre 19.63 ne figure nulle part dans la source. Remplacez radius par 3.0, recompilez, et le rayon comme l'aire suivent d'eux-mêmes dans le texte. Avec des nombres saisis à la main, il en reste toujours un qu'on oublie ; ici, il n'y a rien à oublier. Peu de techniques garantissent à si bas prix que les nombres d'un article ne peuvent pas contredire le code qui les a produits.

L'idée elle-même n'a rien de neuf. WEB, l'outil que Donald Knuth a construit en 1984 pour ce qu'il a nommé la programmation lettrée, permettait d'écrire de la prose à l'intérieur d'un programme Pascal : tangle en extrayait le Pascal, weave en extrayait le TeX. PythonTeX retourne le dispositif. Le document maître reste du LaTeX, et c'est le programme qui vient s'y installer. Dans un sens comme dans l'autre, la motivation est la même : séparez l'explication de l'implémentation en deux fichiers et, tôt ou tard, elles se contrediront.

\py, \pyc, pycode, pyblock : choisir par le suffixe

Ces noms ne s'apprennent pas par cœur ; deux questions les déterminent. Ce code doit-il être exécuté ? Doit-il être montré sur la page ? La combinaison des deux réponses donne le suffixe. Pour le nom de base py : sans suffixe, la valeur d'une expression est composée ; c (code) exécute seulement ; v (verb) compose seulement ; b (block) fait les deux. En ligne on emploie la forme commande (\pyc{…}), et sur plusieurs lignes l'environnement de même nom (pycode).

CommandeEnvironnement correspondantExécution / composition
\pyexécute une expression et ne compose que sa forme chaîne
\pycpycodeexécute sans rien composer ; la sortie de print est reprise automatiquement
\pyvpyverbatimn'exécute pas ; compose le code tel quel
\pybpyblockexécute et compose ; la sortie de print n'est pas reprise automatiquement
\pyspysubremplace chaque !{expr} par sa valeur, puis lit le résultat comme du LaTeX
\pyconpyconsoleémule la console interactive et compose >>> avec entrées et sorties

L'argument d'une commande inline fonctionne comme celui de \verb : ce ne sont pas nécessairement des accolades. Toute paire de caractères identiques convient, si bien que \py{2**10}, \py#2**10# et \py@2**10@ ont le même sens — échappatoire commode quand le code contient lui-même des accolades. Une seule restriction est à respecter : \py insère une valeur et n'accepte donc pas d'affectation. Le manuel déclare \py{a=1} explicitement invalide, au motif qu'une affectation n'a pas de représentation sous forme de chaîne. Créer des variables revient à pycode ; \py{a} se contente de les rappeler.

Le traitement de print s'inverse selon que le code est montré ou non, comme le laisse entendre le tableau. Là où le code reste caché — pycode, \pyc —, l'option de package autoprint (active par défaut) verse la sortie sur place. Là où le code est montré — pyblock, \pyb —, l'insertion automatique cesse, au motif qu'on souhaite rarement voir la sortie collée sous le listing qui l'a produite. Placez \printpythontex (ou \stdoutpythontex) à l'endroit voulu. On peut aussi la mettre de côté sous un nom avec \saveprintpythontex{name} et la rappeler plus loin par \useprintpythontex{name}.

Les supports pédagogiques et les articles techniques réclament sans cesse une session interactive reproduite. L'environnement pyconsole traite son contenu comme s'il avait été saisi dans un interpréteur et, à l'aide du module code de Python, entrelace entrées et sorties. L'exemple ci-dessous donne trois lignes — >>> a = 1, >>> a + 3, 4 — et ce 4 n'a pas été écrit : il a été calculé pendant la compilation. Pour une construction sur plusieurs lignes, comme une définition de fonction, une ligne vide après la dernière peut être nécessaire. La même famille propose aussi \pyconv / pyconverbatim, qui composent une session collée sans l'exécuter, et \pyconc / pyconcode, qui exécutent sans composer.

latex
\begin{pyconsole}
a = 1
a + 3
\end{pyconsole}

% typeset result:
%   >>> a = 1
%   >>> a + 3
%   4

La compilation en trois étapes, et pourquoi -shell-escape est inutile

Un document PythonTeX se construit en trois passes : LaTeX, puis pythontex, puis LaTeX de nouveau. La première passe LaTeX n'exécute rien ; elle se contente d'extraire le code vers un fichier externe nommé <jobname>.pytxcode. Le programme pythontex exécute ensuite ce code et enregistre les résultats, et la seconde passe LaTeX les récupère pour produire le PDF. Ne lancer le moteur qu'une fois, et les valeurs patiemment écrites n'apparaissent nulle part : c'est le faux pas classique.

terminal
pdflatex document.tex    # 1) LaTeX extracts the code to document.pytxcode
pythontex document.tex   # 2) a separate program runs it and caches the results
pdflatex document.tex    # 3) LaTeX pulls the results back into the document

Voici le fait qui surprend quiconque connaît minted : PythonTeX n'a pas besoin de -shell-escape. minted lance un programme externe en pleine composition, d'où l'arrêt sur ! Package minted Error: You must invoke LaTeX with the -shell-escape flag. (voir « Listings de code »). Avec PythonTeX, en revanche, le code n'est pas exécuté par LaTeX mais par un programme distinct, intercalé entre les deux passes LaTeX. LaTeX se borne à écrire le .pytxcode puis à relire les résultats. De fait, pythontex.sty ne contient pas un seul emploi de \write18.

Cette conception « intercalée » a un autre effet agréable. Le fichier .pytxcode consigne non seulement chaque bloc de code mais aussi la ligne du fichier .tex dont il provient. Quand Python trébuche, pythontex indique donc le numéro de ligne du manuscrit et non celui du .py engendré. Employez un nom non défini dans un bloc pycode et vous obtenez * PythonTeX stderr - error on line 8: suivi de NameError: name 'nosuchname' is not defined — et ce 8 désigne la ligne 8 du .tex. Nul besoin d'ouvrir le fichier engendré pour recompter les lignes.

Taper trois commandes à chaque fois n'est pas réaliste ; en pratique, on confie le travail à latexmk. La configuration donnée par le manuel déclare le fichier de code extrait, .pytxcode, comme dépendance et lance pythontex dès qu'il change ; lorsque pythontex réécrit ses fichiers de sortie, latexmk le détecte et recompile de lui-même. Là non plus, le shell escape n'intervient pas : latexmk appelle simplement pythontex comme une commande externe ordinaire.

.latexmkrc
# run pythontex whenever the extracted code changes
add_cus_dep('pytxcode', 'tex', 0, 'pythontex');
sub pythontex { return system("pythontex \"$_[0]\""); }

Le moteur importe peu. Remplacez pdflatex par lualatex ou xelatex, ou par platex pour un document japonais : la forme en trois étapes ne change pas. Les caractères non ASCII dans le code exigent en revanche une configuration du document, et le manuel est précis : sous pdfLaTeX, \usepackage[T1]{fontenc} avec \usepackage[utf8]{inputenc} ; sous LuaLaTeX, \usepackage{fontspec} ; sous XeLaTeX, la même chose plus \defaultfontfeatures{Ligatures=TeX}. Un piège propre à XeLaTeX : si le code contient des tabulations, compilez avec -8bit, faute de quoi elles sont écrites sous la forme de la séquence ^^I.

Pourquoi les recompilations restent rapides : cache, sessions et --rerun

Le code inchangé n'est pas exécuté. C'est ce qui rend praticable l'idée en apparence téméraire d'enfouir de gros calculs dans un document. pythontex enregistre ses résultats sous pythontex-files-<jobname>/ — le cache proprement dit tient dans pythontex_data.pkl — et, à l'exécution suivante, ne relance que les blocs modifiés. Corriger une coquille dans un paragraphe ne relance pas la simulation de trente secondes.

Ce qui compte comme « modifié » se règle par --rerun, doublé d'une option de package équivalente, \usepackage[rerun=…]{pythontex}. La valeur par défaut est errors : tout ce qui a changé, plus tout ce qui a produit une erreur la fois précédente. C'est pourquoi un bloc en échec est réessayé pendant le débogage sans qu'on ait à y toucher. Les seuils forment une échelle.

  • never — n'exécuter rien ; se contenter d'un avertissement si du code a changé.
  • modified — n'exécuter que les blocs modifiés (ou dont les dépendances ont changé).
  • errorsvaleur par défaut. Tout ce qui a changé, plus tout ce qui a échoué la fois précédente.
  • warnings — en plus, réexécuter tout ce qui a produit un avertissement la fois précédente.
  • always — tout exécuter à chaque fois ; pour l'essentiel équivalent à --runall.

Le point faible du cache, c'est le code inchangé qui lit des données modifiées. Déclarez-les côté Python par pytex.add_dependencies('data.csv') : le bloc est réexécuté précisément quand ce fichier change — d'après la date de modification par défaut, ou d'après un hachage avec --hashdependencies. Les fichiers créés peuvent être enregistrés par pytex.add_created() afin d'être nettoyés ensuite. Notez encore que les sessions tournent en parallèle : les blocs séparés par \begin{pycode}[sessionname] deviennent des processus distincts, et leur nombre simultané vaut par défaut le nombre de cœurs du processeur (--jobs le modifie). Si les comptes ne tombent toujours pas juste, l'ultime recours proposé par le manuel est de supprimer entièrement pythontex-files-<jobname>/ et de reconstruire.

Faire entrer une figure matplotlib et de l'algèbre SymPy dans le document

Produire des figures est d'une simplicité rafraîchissante : faire appeler savefig à matplotlib dans un bloc pycode, puis inclure le fichier par \includegraphics. Par défaut il est écrit à côté du .tex, donc aucun chemin à gérer (\setpythontexworkingdir le modifie au besoin). L'intéressant vient ensuite : écrivez \setpythontexcontext{textwidth=\the\textwidth} et les dimensions de LaTeX passent du côté Python, lisibles comme pytex.context.textwidth ; après conversion par pytex.pt_to_in(), on peut construire une figure exactement à la largeur du bloc de texte. Comme rien n'est redimensionné ensuite, les libellés de la figure sortent à la même taille que le texte alentour.

document.tex
\documentclass{article}
\usepackage{graphicx}
\usepackage{pythontex}
\setpythontexcontext{textwidth=\the\textwidth}

\begin{document}
\begin{pycode}
import matplotlib
matplotlib.use('pgf')
import matplotlib.pyplot as plt
import numpy as np

width = pytex.pt_to_in(pytex.context.textwidth)
x = np.linspace(0, 2*np.pi, 200)
fig, ax = plt.subplots(figsize=(width, 0.4*width))
ax.plot(x, np.sin(x))
fig.savefig('wave.pdf', bbox_inches='tight')
\end{pycode}

\includegraphics{wave.pdf}
\end{document}

Et voici le nid-de-poule que la première compilation heurte presque toujours. Au moment de la première passe LaTeX, wave.pdf n'existe pas encore : on lit donc ! Package pdftex.def Error: File 'wave.pdf' not found: using draft setting. Rien n'est cassé — la figure est créée à la deuxième étape, par pythontex ; une fois les trois étapes parcourues, la seconde passe LaTeX la trouve. Ne pas faire demi-tour devant cette ligne, persuadé d'avoir mal configuré quelque chose, est la première astuce à connaître.

Pour les mathématiques, des familles dédiées existent. Remplacez le nom de base py par un autre et vous retrouvez exactement le même attelage : \sympy, sympycode, sympyblock d'une part, \pylab, pylabcode, pylabblock de l'autre. Seuls diffèrent l'import initial et la manière de présenter le résultat.

  • La famille sympy — charge la bibliothèque de calcul symbolique SymPy par from sympy import *. Une expression insérée par \sympy passe par le LatexPrinter de SymPy, qui la met en forme selon le contexte, en ligne ou hors texte. C'est ce qui rend possibles des tours de force comme une table complète de dérivées et d'intégrales engendrée automatiquement.
  • La famille pylab — charge le module pylab de matplotlib par from pylab import *, réunissant tracés et NumPy dans un même espace de noms. Si l'on préfère écrire ses imports soi-même, comme dans l'exemple ci-dessus, la simple famille py suffit.

Quand la revue ne peut pas le compiler : depythontex et la question de sécurité

Voilà la vraie contrainte de PythonTeX. Une chaîne de traitement qui ne lance qu'un moteur LaTeX n'achèvera jamais ce document. Ce qui manque n'est pas l'autorisation du shell escape, mais la passe pythontex intercalée elle-même. Le manuel le concède : les documents utilisant PythonTeX conviennent moins bien que du LaTeX ordinaire à la soumission, au partage et à la conversion vers d'autres formats. C'est précisément à cela que sert depythontex. Compilez avec \usepackage[depythontex]{pythontex} : un fichier auxiliaire <jobname>.depytx apparaît ; le script depythontex le confronte à la source d'origine et écrit un second .textoute commande et tout environnement PythonTeX ont été remplacés par le code composé et sa sortie — du LaTeX ordinaire, résultats intégrés, sans la moindre dépendance à PythonTeX.

terminal
# 1) run the usual three steps, with the depythontex package option on
pdflatex document.tex
pythontex document.tex
pdflatex document.tex

# 2) write the static, PythonTeX-free copy
depythontex -o document-plain.tex document.tex

# code display in the output can be switched to another package
depythontex --listing minted -o document-plain.tex document.tex

--listing rend discrètement service. Il permet de choisir la présentation du code dans la version statique — verbatim, fancyvrb, listings, minted ou pythontex —, de sorte qu'une consigne de soumission exigeant listings ne pose aucun problème (voir « Listings de code »). Il existe une voie plus légère encore : le manuel signale que, s'il s'agit seulement de transmettre le document à un coauteur, il suffit de joindre pythontex.sty et le répertoire de sortie. Le destinataire peut alors éditer tout ce qui n'est pas du Python comme un document LaTeX ordinaire, sans jamais lancer Python.

Pour finir, le point que le manuel place dans un encadré d'avertissement. Compiler un document qui utilise PythonTeX, c'est exécuter réellement Python — et parfois d'autres programmes — sur sa propre machine. Il ne faut donc compiler que des documents dont la provenance est sûre. Que -shell-escape soit inutile ne rend pas la chose plus sûre : le code s'exécute pareillement, simplement depuis l'extérieur de LaTeX plutôt que de l'intérieur.