PythonTeX (嵌入输出)

PythonTeX(Geoffrey M. Poore 编写)是一个宏包,会在编译时 实际运行你写在文档中的 Python 代码,并把结果嵌回正文。图表、表格和计算结果与生成它们的代码放在同一份源码中,因此不用担心手动抄错数值;修改源码再编译,结果也会自动更新。除了 Python,它还可以驱动 Ruby、Julia、R、Octave 等语言。

它做什么

普通的 listingsminted 只是把代码 按外观排版出来,并不会执行(参见“代码清单”)。PythonTeX 的不同之处在于,它会 执行代码,并把输出纳入文档。例如在正文中写 \py{2**10},Python 会计算 2**10,结果 1024 就排在当前位置。写一段绘图的 Python 代码,生成的图会直接作为图形出现。

载入只需一行 \usepackage{pythontex}。运行时需要 Python 本体,代码着色(语法高亮)使用 Pygmentspip install pygments)。代码按“会话”分进程运行,而且只重新执行 发生变化的部分,因此即使包含较重的计算,再编译也能保持轻量。

命令和环境以一个共同的 基底名(如 py)加后缀 组成一组家族。要“行内执行一个表达式”、还是“运行整块代码”,要“代码也一起显示”、还是“只要结果”,都由具体后缀来选择。

主要命令和环境

先看 行内命令\py{expr} 会把表达式交给 Python,并排版其字符串表示(\py{2**10} → 1024)。除花括号外,也可以使用一对相同的定界符,所以 \py#2**10#\py@2**10@ 含义相同。它是用来把值插入文档的,不能赋值\py{a=1} 不可用)。先在前面建立变量后,就可以用 \py{a} 调出它的值。

  • \py{expr} — 执行表达式并排版结果(无后缀)。
  • \pyc{code} — 执行代码,但不排版任何内容(c 表示 code)。如果其中 print,输出会被纳入。
  • \pyv{code} — 不执行,只把代码 原样排版v 表示 verb,相当于 pyverbatim)。
  • \pyb{code}既执行又排版b 表示 block)。print 的输出不会自动插入。
  • \pys{code} — 字符串插值。先用求值结果替换 !{expr},再把代码作为 LaTeX 解释(s 表示 sub)。

多行代码使用 环境pycode 只执行其中的代码,不排版(最适合计算和生成图形)。pyverbatim 只排版,不执行。pyblock 既执行又排版pysub\pys 的环境版(对 !{expr} 做插值)。pyblock\pybprint 输出不会自动出现;想插入的位置需要放置 \printpythontex

想展示交互式会话时,用 pyconsole 环境。其中每一行都会像输入到交互式 Python 解释器中一样处理,并复现 >>> 提示符以及输入输出。行内版 \pycon{code} 会执行代码并只取回输出(丢弃输入),适合引用控制台变量的值。

一个示例

典型流程是先在 pycode 环境中计算值,再在正文中用 \py{…} 排版这些值。注意,代码和结果都完整地留在同一份原稿中。

document.tex
\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage{pythontex}

\begin{document}
% 実行されるが、何も組版されない
\begin{pycode}
from math import sqrt
radius = 2.5
area = 3.14159 * radius**2
hyp = sqrt(3**2 + 4**2)
\end{pycode}

半径 \py{radius} の円の面積はおよそ \py{round(area, 2)} です。

\(2^{10} = \py{2**10}\)、また \(\sqrt{3^2+4^2} = \py{hyp}\)\end{document}

这会得到“半径为 2.5 的圆面积约为 19.63”这样的句子,并排出 2¹⁰ = 1024、√(3²+4²) = 5.0 这些公式。改变 radius 后重新构建,正文中的所有数值都会随之更新。

构建流程(三步)

PythonTeX 的构建有 三个阶段。第一,照常运行 LaTeX 引擎(如 pdflatex);这一轮不会执行正文中的代码,而是把代码 抽取 到外部文件。第二,附带的 pythontex 程序 执行这些抽取出的代码并保存结果。第三,再运行一次 LaTeX 引擎,保存好的结果被 读回 文档,生成 PDF。

terminal
pdflatex document.tex    # 1) コードを抽出 / extract the code
pythontex document.tex   # 2) Python で実行 / run it with Python
pdflatex document.tex    # 3) 結果を取り込む / pull the output back in

关键在这里:执行代码的不是 LaTeX 本身,而是夹在中间的 独立程序 pythontex。因此,手动的三步构建 不需要 --shell-escape(这正是它与 minted 的不同点,后者从 LaTeX 内部调用外部命令)。另一方面,如果想用 latexmk 等工具把它 自动化成一次编译,常见设置会启用 -shell-escape,让 LaTeX 流程调用 pythontex。在 .latexmkrc 中声明依赖关系后,代码变化时就能自动运行 pythontex 并重新编译。

日文文档中也可以使用。把 pdflatex 换成 lualatex,无论引擎如何,仍然是同样的三步流程(日文 LaTeX 的 platex 也可用)。处理包含 Unicode 的代码时,要设置好文档端的编码;pdfLaTeX 下使用 \usepackage[T1]{fontenc}\usepackage[utf8]{inputenc},LuaLaTeX 下则使用 \usepackage{fontspec} 等。

sympy 与 pylab 家族

默认情况下,除了 Python 的 py 家族,还提供两个面向科学计算的家族。它们只是把基底名换成 sympypylab,命令阵容相同,例如 \sympysympycodesympyblock,以及 \pylabpylabcodepylabblock

  • sympy 家族 — 通过 from sympy import * 载入符号计算库 SymPy。用 \sympy 插入的结果会使用 SymPy 的 LatexPrinter 按上下文整理成 LaTeX 形式。
  • pylab 家族 — 通过 from pylab import * 载入 matplotlib 的 pylab 模块,把绘图和 NumPy 放在同一命名空间中。

各家族可以作为彼此独立的命令和环境集合并行使用。例如,可以把 SymPy 符号求解出的表达式用 \sympy 排成漂亮的数学式,同时在 pylabcode 中生成 matplotlib 图形,再用 \includegraphics 插入。

depythontex 与安全性

使用 PythonTeX 的文档混合了 LaTeX 和 Python,因此不适合不接受特殊宏包的投稿系统,也不适合直接转换为其他格式。这时 depythontex 很有用。启用宏包选项 depythontex 后构建,会生成辅助文件 <jobname>.depytxdepythontex.py 脚本会把它与原稿对应起来,生成一份把 所有 PythonTeX 命令和环境都替换为已排版代码和输出 的独立文档。结果是已经烘焙好结果、且不依赖 PythonTeX 的普通 LaTeX,适合共享或论文投稿。

terminal
# 1) depythontex オプション付きで通常の 3 ステップを実行
#    Run the usual three steps with the depythontex option on
pdflatex document.tex
pythontex document.tex
pdflatex document.tex

# 2) 静的な版を書き出す(-o で出力名を指定)
#    Write the static version (-o gives the output name)
depythontex -o document-plain.tex document.tex

最后要注意 安全性。编译包含 PythonTeX 的文档,会在你的计算机上 实际执行 Python(有时还包括其他程序)。因此只应该编译 来自可信来源的文档。只要机制涉及“执行”代码,这一点就和依赖 --shell-escape 的其他方法一样,是无法回避的性质。