根据讨论,问题不是解析,而是输出是关注的对象。如前所述,无法修改默认的表示方法,但这并不排除将特定令牌转换为应用程序用户可能希望看到的表示的可能性。阅读文档,有一个printing 模块将提供必要的控制级别。
以下是控制Floats 呈现方式的最基本实现,通过扩展提供的StrPrinter:
from sympy.printing.str import StrPrinter
class CustomStrPrinter(StrPrinter):
def _print_Float(self, expr):
return '{:.2e}'.format(expr)
现在有了控制输出的能力,给它一个方程
>>> stmt = parse_expr('2*x + 3**2 + 1e10', evaluate=False)
>>> CustomStrPrinter().doprint(stmt)
'2*x + 3**2 + 1.00e+10'
请注意,我提供的实现将输出限制为两位数 - 您需要针对您的特定用例进行修改,因为 this thread 可能会给您一些指示。
另外,咨询parsing transformation 部分(和相关代码)可能会有用。
自然,更深入地研究 sympy 实际处理解析的方式(这实际上是我第一次查看这个库;在我目前尝试回答这个问题之前,我有 零 的经验),我注意到转换可能有用。
查看parse_expr 是如何实现的,我注意到它调用stringify_expr,并且考虑到转换实际上应用在该位置,它向我表明在转换步骤之前和之后比较tokens 的列表将是一个攻击的好地方。
这样,启动调试器(一些输出被截断)
>>> from sympy.parsing.sympy_parser import parse_expr, standard_transformations
>>> import pdb
>>> pdb.run("parse_expr('1e10', transformations=standard_transformations)")
> <string>(1)<module>()
(Pdb) s
--Call--
> /tmp/env/lib/python3.7/site-packages/sympy/parsing/sympy_parser.py(908)parse_expr()
(Pdb) b 890
(Pdb) c
-> for transform in transformations:
(Pdb) pp tokens
[(2, '1e10'), (0, '')]
(Pdb) clear
(Pdb) b 893
(Pdb) c
> /tmp/env/lib/python3.7/site-packages/sympy/parsing/sympy_parser.py(893)stringify_expr()
-> return untokenize(tokens)
(Pdb) pp tokens
[(1, 'Float'), (53, '('), (2, "'1e10'"), (53, ')'), (0, '')]
(顺便说一句,在 sympy “编译”之前,这些标记将被取消标记回 Python 源代码)
自然地,一个简单的float 会被转换为sympy.core.numbers.Float,并且阅读该文档表明可以控制精度,这向我表明这可能会对输出产生影响,让我们尝试一下:
>>> from sympy.core.numbers import Float
>>> Float('1e10', '1')
1.e+10
到目前为止一切顺利,看起来正是您想要的。然而,默认的auto_number 实现不提供任何东西来切换Float 节点的构造方式,但是考虑到转换只是简单的函数,并且可以在应用auto_number 转换之后根据该输出的特征来实现。可以做到以下几点:
from ast import literal_eval
from tokenize import NUMBER, NAME, OP
def restrict_e_notation_precision(tokens, local_dict, global_dict):
"""
Restrict input e notation precision to the minimum required.
Should be used after auto_number transformation, as it depends on
that transform to add the ``Float`` functional call for this to have
an effect.
"""
result = []
float_call = False
for toknum, tokval in tokens:
if toknum == NAME and tokval == 'Float':
# set the flag
float_call = True
if float_call and toknum == NUMBER and ('e' in tokval or 'E' in tokval):
# recover original number before auto_number transformation
number = literal_eval(tokval)
# split the significand from base, while dropping the
# decimal point before treating length as the precision.
precision = len(number.lower().split('e')[0].replace('.', ''))
result.extend([(NUMBER, repr(str(number))), (OP, ','),
(NUMBER, repr(str(precision)))])
float_call = False
else:
result.append((toknum, tokval))
return result
现在按照使用 parse_expr 的文档进行自定义转换,如下所示:
>>> from sympy.parsing.sympy_parser import parse_expr, standard_transformations
>>> transforms = standard_transformations + (restrict_e_notation_precision,)
>>> stmt = parse_expr('2.412*x**2 + 1.14e-5 + 1e10', evaluate=False, transformations=transforms)
>>> print(stmt)
2.412*x**2 + 1.14e-5 + 1.0e+10
这看起来正是您想要的。但是,仍有一些情况需要自定义打印机来解决,例如:
>>> stmt = parse_expr('3.21e2*x + 1.3e-3', evaluate=False, transformations=transforms)
>>> stmt
321.0*x + 0.0013
这又是由于 Python 中 float 类型的默认 repr 施加的限制。使用此版本的 CustomStrPrinter 来检查并利用我们在此处自定义转换中指定的精度将解决此问题:
from sympy.printing.str import StrPrinter
class FloatPrecStrPrinter(StrPrinter):
"""
A printer that checks for custom precision and output concisely.
"""
def _print_Float(self, expr):
if expr._prec != 53: # not using default precision
return ('{:.%de}' % ((int(expr._prec) - 5) // 3)).format(expr)
return super()._print_Float(expr)
演示:
>>> from sympy.parsing.sympy_parser import parse_expr, standard_transformations
>>> transforms = standard_transformations + (restrict_e_notation_precision,)
>>> stmt = parse_expr('3.21e2*x + 1.3e-3 + 2.7', evaluate=False, transformations=transforms)
>>> FloatPrecStrPrinter().doprint(stmt)
'3.21e+2*x + 1.3e-3 + 2.7'
需要注意的是,我上面的解决方案利用了浮点数的各种特性——如果在计算中使用输入表达式,它可能不会给出任何正确的输出,并且使用的数字和任何方程主要用作粗略的指导而且我没有对所有事情都进行任何边缘情况调查 - 我将把它留给你,因为这已经花费了我很多时间来深入研究并写下整个事情 - 例如。您可能希望将其用于向用户显示,但在内部保留其他纯粹的表达式(即没有此额外转换)以进行算术运算。
无论如何,往前走,记下所依赖的库的实现细节绝对是有用的,不要害怕使用调试器来找出真正发生的事情并充分利用图书馆制造商提供给用户的钩子供他们扩展。