将Python数值表达式转换为LaTeX

39 投票
6 回答
27829 浏览
提问于 2025-04-16 05:02

我需要把一些符合Python语法的字符串转换成:

'1+2**(x+y)'

然后得到相应的LaTeX格式:

$1+2^{x+y}$

我试过使用SymPy的latex函数,但它处理的是实际的表达式,而不是字符串形式:

>>> latex(1+2**(x+y))
'$1 + 2^{x + y}$'
>>> latex('1+2**(x+y)')
'$1+2**(x+y)$'

而且要做到这一点,还需要先把xy声明为“符号”类型。

我想要一个更简单的方法,最好能用编译器模块里的解析器来实现。

>>> compiler.parse('1+2**(x+y)')
Module(None, Stmt([Discard(Add((Const(1), Power((Const(2), Add((Name('x'), Name('y'))))))))]))

最后,为什么要这样做:我需要生成这些LaTeX代码片段,以便在网页上用MathJax展示。

6 个回答

17

你可以使用SymPy这个工具。首先,把你想要的字符串传给sympify()这个函数,它会把这个字符串转换成一个有效的SymPy表达式(也就是说,它会为你创建符号等等)。所以你可以这样做:

>>> latex(sympify('1+2**(x+y)'))
1 + 2^{x + y}

S()也是sympify()的一个快捷方式,也就是说,latex(S('1+2**(x+y)'))这样写也是可以的。

29

这里有一个比较长但仍然不完整的方法,它并没有使用sympy。这个方法足以处理一个例子:(-b-sqrt(b**2-4*a*c))/(2*a),这个表达式可以转化为\frac{- b - \sqrt{b^{2} - 4 \; a \; c}}{2 \; a},并且显示为:

alt text

这个方法基本上是创建一个抽象语法树(AST),然后遍历它,生成与AST节点对应的latex数学表达式。这里的内容应该能给你一个大致的思路,帮助你在不足的地方进行扩展。


import ast

class LatexVisitor(ast.NodeVisitor):

    def prec(self, n):
        return getattr(self, 'prec_'+n.__class__.__name__, getattr(self, 'generic_prec'))(n)

    def visit_Call(self, n):
        func = self.visit(n.func)
        args = ', '.join(map(self.visit, n.args))
        if func == 'sqrt':
            return '\sqrt{%s}' % args
        else:
            return r'\operatorname{%s}\left(%s\right)' % (func, args)

    def prec_Call(self, n):
        return 1000

    def visit_Name(self, n):
        return n.id

    def prec_Name(self, n):
        return 1000

    def visit_UnaryOp(self, n):
        if self.prec(n.op) > self.prec(n.operand):
            return r'%s \left(%s\right)' % (self.visit(n.op), self.visit(n.operand))
        else:
            return r'%s %s' % (self.visit(n.op), self.visit(n.operand))

    def prec_UnaryOp(self, n):
        return self.prec(n.op)

    def visit_BinOp(self, n):
        if self.prec(n.op) > self.prec(n.left):
            left = r'\left(%s\right)' % self.visit(n.left)
        else:
            left = self.visit(n.left)
        if self.prec(n.op) > self.prec(n.right):
            right = r'\left(%s\right)' % self.visit(n.right)
        else:
            right = self.visit(n.right)
        if isinstance(n.op, ast.Div):
            return r'\frac{%s}{%s}' % (self.visit(n.left), self.visit(n.right))
        elif isinstance(n.op, ast.FloorDiv):
            return r'\left\lfloor\frac{%s}{%s}\right\rfloor' % (self.visit(n.left), self.visit(n.right))
        elif isinstance(n.op, ast.Pow):
            return r'%s^{%s}' % (left, self.visit(n.right))
        else:
            return r'%s %s %s' % (left, self.visit(n.op), right)

    def prec_BinOp(self, n):
        return self.prec(n.op)

    def visit_Sub(self, n):
        return '-'

    def prec_Sub(self, n):
        return 300

    def visit_Add(self, n):
        return '+'

    def prec_Add(self, n):
        return 300

    def visit_Mult(self, n):
        return '\\;'

    def prec_Mult(self, n):
        return 400

    def visit_Mod(self, n):
        return '\\bmod'

    def prec_Mod(self, n):
        return 500

    def prec_Pow(self, n):
        return 700

    def prec_Div(self, n):
        return 400

    def prec_FloorDiv(self, n):
        return 400

    def visit_LShift(self, n):
        return '\\operatorname{shiftLeft}'

    def visit_RShift(self, n):
        return '\\operatorname{shiftRight}'

    def visit_BitOr(self, n):
        return '\\operatorname{or}'

    def visit_BitXor(self, n):
        return '\\operatorname{xor}'

    def visit_BitAnd(self, n):
        return '\\operatorname{and}'

    def visit_Invert(self, n):
        return '\\operatorname{invert}'

    def prec_Invert(self, n):
        return 800

    def visit_Not(self, n):
        return '\\neg'

    def prec_Not(self, n):
        return 800

    def visit_UAdd(self, n):
        return '+'

    def prec_UAdd(self, n):
        return 800

    def visit_USub(self, n):
        return '-'

    def prec_USub(self, n):
        return 800
    def visit_Num(self, n):
        return str(n.n)

    def prec_Num(self, n):
        return 1000

    def generic_visit(self, n):
        if isinstance(n, ast.AST):
            return r'' % (n.__class__.__name__, ', '.join(map(self.visit, [getattr(n, f) for f in n._fields])))
        else:
            return str(n)

    def generic_prec(self, n):
        return 0

def py2tex(expr):
    pt = ast.parse(expr)
    return LatexVisitor().visit(pt.body[0].value)

24

你可以用 sympy.latex 配合 eval 来实现:

s = "1+2**(x+y)"
sympy.latex(eval(s))   # prints '$1 + {2}^{x + y}$'

不过,你还是得把变量声明为符号。如果这真的是个麻烦事,其实写一个解析器来处理这个问题要简单得多,而不是从头开始解析所有内容并生成latex。

撰写回答