将Python数值表达式转换为LaTeX
我需要把一些符合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)$'
而且要做到这一点,还需要先把x
和y
声明为“符号”类型。
我想要一个更简单的方法,最好能用编译器模块里的解析器来实现。
>>> 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}
,并且显示为:
这个方法基本上是创建一个抽象语法树(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。