PythonTeX (incrustar salida)

Todo artículo contiene un número calculado en otro sitio —un script, un cuaderno, una hoja de cálculo— y luego tecleado a mano en el texto; basta editar el script para que ese número se vuelva silenciosamente falso. PythonTeX cierra esa grieta: ejecuta, durante la composición, el Python escrito dentro de la fuente LaTeX y compone lo que devuelve. Es obra de Geoffrey M. Poore y, pese al nombre, también maneja Ruby, Julia, R, Octave, Bash, Rust, Perl y JavaScript. Esta página recorre desde \usepackage{pythontex} hasta la familia de comandos \py, pasando por la compilación en tres pasos con la que todo el mundo tropieza una vez, y llega al único caso en que conviene usar otra cosa.

Componer el código o ejecutarlo: PythonTeX frente a listings y minted

listings y minted componen el código tal como se ve y no ejecutan ni una línea. PythonTeX se diferencia en que ejecuta el código y compone la cadena que devuelve. Al escribir \py{2**10} en el texto, lo que aparece no son los caracteres 2**10 sino el resultado, 1024. Cargarlo son dos palabras, \usepackage{pythontex}; ejecutarlo exige, junto a la distribución de TeX, Python y además Pygments para el resaltado de sintaxis.

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}

Este documento compone «A circle of radius 2.5 has area 19.63.», seguido de 2¹⁰ = 1024 y √(3²+4²) = 5.0. Lo importante es que la cifra 19.63 no aparece en ningún punto de la fuente. Cambie radius a 3.0, reconstruya, y tanto el radio como el área del texto le siguen solos. Con números tecleados a mano siempre queda uno sin actualizar; aquí no hay nada que olvidar. Pocas técnicas garantizan a un precio tan bajo que las cifras de un artículo no puedan contradecir al código que las produjo.

La idea en sí no es nueva. WEB, la herramienta que Donald Knuth construyó en 1984 para lo que llamó programación literaria, permitía escribir prosa dentro de un programa en Pascal: tangle extraía el Pascal y weave extraía el TeX. PythonTeX le da la vuelta. El documento principal sigue siendo LaTeX y es el programa el que se muda a vivir dentro. En cualquiera de los dos sentidos la motivación es idéntica: si la explicación y la implementación viven en archivos distintos, tarde o temprano dejarán de coincidir.

\py, \pyc, pycode, pyblock: elegir por el sufijo

Estos nombres no se memorizan; los deciden dos preguntas. ¿Se ejecuta ese código? ¿Se muestra en la página? La combinación de ambas respuestas es el sufijo. Con el nombre base py: sin añadidos compone el valor de una expresión; c (code) solo lo ejecuta; v (verb) solo lo compone; b (block) hace ambas cosas. En línea se usa la forma de comando (\pyc{…}) y, para varias líneas, el entorno del mismo nombre (pycode).

ComandoEntorno correspondienteEjecución / composición
\pyejecuta una expresión y compone solo su forma de cadena
\pycpycodeejecuta sin componer nada; la salida de print se incorpora sola
\pyvpyverbatimno ejecuta; compone el código tal cual
\pybpyblockejecuta y compone; la salida de print no se incorpora sola
\pyspysubsustituye cada !{expr} por su valor y lee el resultado como LaTeX
\pyconpyconsoleemula la consola interactiva y compone >>> con entrada y salida

El argumento de un comando en línea funciona como el de \verb: no tienen por qué ser llaves. Sirve cualquier pareja de caracteres idénticos, de modo que \py{2**10}, \py#2**10# y \py@2**10@ significan lo mismo, una salida cómoda cuando el propio código contiene llaves. Solo hay una restricción que respetar: \py inserta un valor y por tanto no admite asignaciones. El manual declara \py{a=1} explícitamente inválido, porque una asignación no tiene representación como cadena. Crear variables corresponde a pycode; \py{a} se limita a recuperarlas.

El tratamiento de print se invierte según se muestre o no el código, como sugiere la tabla. Donde el código queda oculto —pycode, \pyc— la opción de paquete autoprint (activa por omisión) vierte la salida en ese mismo punto. Donde el código se muestra —pyblock, \pyb— la inserción automática se detiene, con el argumento de que rara vez se quiere la salida pegada bajo el listado que la generó. Coloque \printpythontex (o \stdoutpythontex) donde sí la quiera. También puede guardarla con un nombre mediante \saveprintpythontex{name} y recuperarla más lejos con \useprintpythontex{name}.

El material docente y los textos técnicos piden constantemente una sesión interactiva reproducida. El entorno pyconsole trata su contenido como si se hubiera tecleado en un intérprete y, mediante el módulo code del propio Python, alterna entrada y salida. El ejemplo de abajo se compone en tres líneas —>>> a = 1, >>> a + 3, 4— y ese 4 no lo escribió usted: se calculó durante la compilación. Al introducir una construcción de varias líneas, como una definición de función, puede hacer falta una línea en blanco tras la última. La misma familia ofrece además \pyconv / pyconverbatim, que componen una sesión pegada sin ejecutarla, y \pyconc / pyconcode, que ejecutan sin componer.

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

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

La compilación en tres pasos y por qué no hace falta -shell-escape

Un documento con PythonTeX se construye en tres pasadas: LaTeX, luego pythontex, luego LaTeX otra vez. La primera pasada de LaTeX no ejecuta nada; se limita a extraer el código a un archivo externo llamado <jobname>.pytxcode. Después el programa pythontex ejecuta ese código y guarda los resultados, y la segunda pasada de LaTeX los recoge y genera el PDF. Si solo se lanza el motor una vez, los valores escritos con esmero no aparecen por ninguna parte: ese es el tropiezo clásico.

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

Aquí llega el dato que sorprende a quien conoce minted: PythonTeX no necesita -shell-escape. minted lanza un programa externo en plena composición, y por eso se detiene con ! Package minted Error: You must invoke LaTeX with the -shell-escape flag. (véase «Listados de código»). En PythonTeX, en cambio, el código no lo ejecuta LaTeX sino un programa aparte, intercalado entre las dos pasadas de LaTeX. LaTeX se limita a escribir el .pytxcode y más tarde a releer los resultados. De hecho, pythontex.sty no contiene ni un solo uso de \write18.

Ese diseño «intercalado» tiene otro efecto agradable. El archivo .pytxcode registra no solo cada fragmento de código, sino también de qué línea del archivo .tex procede. Así, cuando Python falla, pythontex informa del número de línea del manuscrito y no del .py generado. Use un nombre no definido dentro de un bloque pycode y obtendrá * PythonTeX stderr - error on line 8: seguido de NameError: name 'nosuchname' is not defined, y ese 8 es la línea 8 del .tex. No hay que abrir el archivo generado ni contar líneas a mano.

Teclear tres órdenes cada vez no es realista, así que en la práctica el trabajo se delega en latexmk. La configuración que ofrece el manual declara como dependencia el archivo de código extraído, .pytxcode, y lanza pythontex en cuanto cambia; cuando pythontex reescribe sus archivos de salida, latexmk lo detecta y recompila por su cuenta. Tampoco aquí interviene el shell escape: latexmk invoca pythontex como una orden externa cualquiera.

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

El motor da igual. Cambie pdflatex por lualatex o xelatex, o por platex en un documento japonés, y la forma en tres pasos no varía. Los caracteres no ASCII dentro del código sí exigen preparar el documento, y el manual es concreto: bajo pdfLaTeX, \usepackage[T1]{fontenc} junto con \usepackage[utf8]{inputenc}; bajo LuaLaTeX, \usepackage{fontspec}; bajo XeLaTeX, lo mismo más \defaultfontfeatures{Ligatures=TeX}. Hay una trampa exclusiva de XeLaTeX: si el código contiene tabuladores, compile con -8bit o se escribirán como la secuencia ^^I.

Por qué las reconstrucciones siguen siendo rápidas: caché, sesiones y --rerun

El código que no ha cambiado no se ejecuta. Eso es lo que vuelve practicable la idea, en apariencia temeraria, de empotrar cálculos pesados en un documento. pythontex guarda sus resultados en pythontex-files-<jobname>/ —la caché propiamente dicha está en pythontex_data.pkl— y en la ejecución siguiente solo lanza los fragmentos modificados. Corregir una errata en un párrafo no vuelve a disparar la simulación de treinta segundos.

Qué cuenta como «modificado» se ajusta con --rerun, que tiene una opción de paquete equivalente, \usepackage[rerun=…]{pythontex}. El valor por omisión es errors: todo lo modificado, más todo lo que produjo un error la vez anterior. Por eso, durante la depuración, un bloque que falla se reintenta sin necesidad de tocarlo. Los umbrales forman una escala.

  • never — no ejecutar nada; limitarse a avisar si hay código modificado.
  • modified — ejecutar solo los fragmentos modificados (o con dependencias modificadas).
  • errorsvalor por omisión. Todo lo modificado, más lo que falló la vez anterior.
  • warnings — además, reejecutar lo que produjo una advertencia la vez anterior.
  • always — ejecutarlo todo siempre; en lo esencial equivale a --runall.

El punto débil de la caché es el código que no ha cambiado pero lee datos que sí. Declárelos desde el lado de Python con pytex.add_dependencies('data.csv') y el bloque se reejecutará justo cuando ese archivo se actualice: por fecha de modificación de forma predeterminada, o por hash con --hashdependencies. Los archivos creados pueden registrarse con pytex.add_created() para que se limpien después. Conviene saber además que las sesiones se ejecutan en paralelo: los bloques separados con \begin{pycode}[sessionname] pasan a ser procesos distintos, y el número simultáneo equivale por omisión a la cantidad de núcleos de CPU (--jobs lo cambia). Si aun así las cuentas no cuadran, el último recurso que propone el manual es borrar entero pythontex-files-<jobname>/ y reconstruir.

Llevar una figura de matplotlib y álgebra de SymPy al documento

Producir figuras resulta refrescantemente directo: haga que matplotlib llame a savefig dentro de un bloque pycode y luego incluya el archivo con \includegraphics. Por omisión se escribe junto al .tex, así que no hay ruta que pensar (\setpythontexworkingdir lo cambia si hace falta). Lo interesante viene después: escriba \setpythontexcontext{textwidth=\the\textwidth} y las dimensiones de LaTeX cruzan al lado de Python, legibles como pytex.context.textwidth; con pytex.pt_to_in() se pasan a pulgadas y puede construirse una figura exactamente del ancho de la caja de texto. Como después no se reescala nada, los rótulos de la figura salen del mismo tamaño que el texto que la rodea.

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}

Y aquí está el bache con el que la primera compilación tropieza casi siempre. Cuando corre la primera pasada de LaTeX, wave.pdf todavía no existe, de modo que aparece ! Package pdftex.def Error: File 'wave.pdf' not found: using draft setting. Nada está roto: la figura la crea la segunda etapa, pythontex, así que al recorrer los tres pasos la segunda pasada de LaTeX la encuentra. No dar media vuelta ante esa línea, convencido de haber configurado algo mal, es el primer truco que conviene saber.

Para las matemáticas hay familias hechas a medida. Cambie el nombre base py por otro y obtendrá exactamente el mismo elenco: \sympy, sympycode, sympyblock y \pylab, pylabcode, pylabblock. Lo que cambia es el import inicial y el modo de presentar el resultado.

  • La familia sympy — carga la biblioteca de álgebra simbólica SymPy con from sympy import *. Una expresión insertada con \sympy pasa por el LatexPrinter de SymPy, que la formatea como LaTeX adecuado al contexto, en línea o en display. Eso es lo que hace posibles proezas como generar entera una tabla de derivadas e integrales.
  • La familia pylab — carga el módulo pylab de matplotlib con from pylab import *, reuniendo trazado y NumPy en un solo espacio de nombres. Si prefiere escribir sus propios imports, como en el ejemplo anterior, basta con la familia py a secas.

Cuando la revista no puede compilarlo: depythontex y la cuestión de seguridad

Esta es la verdadera restricción de PythonTeX. Una cadena de procesado que solo ejecuta un motor de LaTeX jamás terminará este documento. Lo que falta no es el permiso de shell escape, sino la propia pasada intermedia de pythontex. El manual lo admite: los documentos que usan PythonTeX resultan menos adecuados que los de LaTeX puro para el envío a revistas, la compartición y la conversión a otros formatos. Justo para eso existe depythontex. Compile con \usepackage[depythontex]{pythontex} y aparecerá un archivo auxiliar <jobname>.depytx; el script depythontex lo coteja con la fuente original y escribe un segundo .tex en el que todo comando y todo entorno de PythonTeX se han sustituido por el código compuesto y su salida: LaTeX corriente, con los resultados incorporados y sin dependencia alguna de 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 cumple discretamente. Permite elegir cómo se muestra el código en la versión estática —verbatim, fancyvrb, listings, minted o pythontex—, de modo que una norma de envío que exija listings no supone obstáculo (véase «Listados de código»). Hay además una vía más ligera: el manual apunta que, si solo hace falta entregar el documento a un coautor, basta con enviar pythontex.sty junto con el directorio de salida. Quien lo reciba podrá editar todo lo que no sea Python como un documento LaTeX corriente, sin ejecutar Python ni una vez.

Por último, el punto que el manual sitúa en un recuadro de advertencia. Compilar un documento que usa PythonTeX significa ejecutar realmente Python —y quizá otros programas— en la propia máquina. Por tanto, compile solo documentos cuya procedencia le merezca confianza. Que no haga falta -shell-escape no lo vuelve más seguro: el código se ejecuta igual, sencillamente desde fuera de LaTeX en lugar de desde dentro.