计算字符串中的数学表达式
- 2024-11-19 08:38:00
- admin 原创
- 10
问题描述:
stringExp = "2^4"
intVal = int(stringExp) # Expected value: 16
这将返回以下错误:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int()
with base 10: '2^4'
我知道eval
可以解决这个问题,但是有没有更好的、更重要的是更安全的方法来评估存储在字符串中的数学表达式?
解决方案 1:
eval
是邪恶的
eval("__import__('os').remove('important file')") # arbitrary commands
eval("9**9**9**9**9**9**9**9", {'__builtins__': None}) # CPU, memory
注意:即使你使用设置__builtins__
为None
,仍然有可能使用自省来突破:
eval('(1).__class__.__bases__[0].__subclasses__()', {'__builtins__': None})
使用以下方式评估算术表达式ast
import ast
import operator as op
# supported operators
operators = {ast.Add: op.add, ast.Sub: op.sub, ast.Mult: op.mul,
ast.Div: op.truediv, ast.Pow: op.pow, ast.BitXor: op.xor,
ast.USub: op.neg}
def eval_expr(expr):
"""
>>> eval_expr('2^6')
4
>>> eval_expr('2**6')
64
>>> eval_expr('1 + 2*3**(4^5) / (6 + -7)')
-5.0
"""
return eval_(ast.parse(expr, mode='eval').body)
def eval_(node):
match node:
case ast.Constant(value) if isinstance(value, int):
return value # integer
case ast.BinOp(left, op, right):
return operators[type(op)](eval_(left), eval_(right))
case ast.UnaryOp(op, operand): # e.g., -1
return operators[type(op)](eval_(operand))
case _:
raise TypeError(node)
您可以轻松限制每个操作或任何中间结果的允许范围,例如限制输入参数a**b
:
def power(a, b):
if any(abs(n) > 100 for n in [a, b]):
raise ValueError((a,b))
return op.pow(a, b)
operators[ast.Pow] = power
或者限制中间结果的大小:
import functools
def limit(max_=None):
"""Return decorator that limits allowed returned values."""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
ret = func(*args, **kwargs)
try:
mag = abs(ret)
except TypeError:
pass # not applicable
else:
if mag > max_:
raise ValueError(ret)
return ret
return wrapper
return decorator
eval_ = limit(max_=10**100)(eval_)
例子
>>> evil = "__import__('os').remove('important file')"
>>> eval_expr(evil) #doctest:+IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
TypeError:
>>> eval_expr("9**9")
387420489
>>> eval_expr("9**9**9**9**9**9**9**9") #doctest:+IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
ValueError:
解决方案 2:
Pyparsing可用于解析数学表达式。特别是,fourFn.py
展示了如何解析基本算术表达式。下面,我将 fourFn 重新包装为数字解析器类,以便于重用。
from __future__ import division
from pyparsing import (Literal, CaselessLiteral, Word, Combine, Group, Optional,
ZeroOrMore, Forward, nums, alphas, oneOf)
import math
import operator
__author__ = 'Paul McGuire'
__version__ = '$Revision: 0.0 $'
__date__ = '$Date: 2009-03-20 $'
__source__ = '''http://pyparsing.wikispaces.com/file/view/fourFn.py
http://pyparsing.wikispaces.com/message/view/home/15549426
'''
__note__ = '''
All I've done is rewrap Paul McGuire's fourFn.py as a class, so I can use it
more easily in other places.
'''
class NumericStringParser(object):
'''
Most of this code comes from the fourFn.py pyparsing example
'''
def pushFirst(self, strg, loc, toks):
self.exprStack.append(toks[0])
def pushUMinus(self, strg, loc, toks):
if toks and toks[0] == '-':
self.exprStack.append('unary -')
def __init__(self):
"""
expop :: '^'
multop :: '*' | '/'
addop :: '+' | '-'
integer :: ['+' | '-'] '0'..'9'+
atom :: PI | E | real | fn '(' expr ')' | '(' expr ')'
factor :: atom [ expop factor ]*
term :: factor [ multop factor ]*
expr :: term [ addop term ]*
"""
point = Literal(".")
e = CaselessLiteral("E")
fnumber = Combine(Word("+-" + nums, nums) +
Optional(point + Optional(Word(nums))) +
Optional(e + Word("+-" + nums, nums)))
ident = Word(alphas, alphas + nums + "_$")
plus = Literal("+")
minus = Literal("-")
mult = Literal("*")
div = Literal("/")
lpar = Literal("(").suppress()
rpar = Literal(")").suppress()
addop = plus | minus
multop = mult | div
expop = Literal("^")
pi = CaselessLiteral("PI")
expr = Forward()
atom = ((Optional(oneOf("- +")) +
(ident + lpar + expr + rpar | pi | e | fnumber).setParseAction(self.pushFirst))
| Optional(oneOf("- +")) + Group(lpar + expr + rpar)
).setParseAction(self.pushUMinus)
# by defining exponentiation as "atom [ ^ factor ]..." instead of
# "atom [ ^ atom ]...", we get right-to-left exponents, instead of left-to-right
# that is, 2^3^2 = 2^(3^2), not (2^3)^2.
factor = Forward()
factor << atom + \n ZeroOrMore((expop + factor).setParseAction(self.pushFirst))
term = factor + \n ZeroOrMore((multop + factor).setParseAction(self.pushFirst))
expr << term + \n ZeroOrMore((addop + term).setParseAction(self.pushFirst))
# addop_term = ( addop + term ).setParseAction( self.pushFirst )
# general_term = term + ZeroOrMore( addop_term ) | OneOrMore( addop_term)
# expr << general_term
self.bnf = expr
# map operator symbols to corresponding arithmetic operations
epsilon = 1e-12
self.opn = {"+": operator.add,
"-": operator.sub,
"*": operator.mul,
"/": operator.truediv,
"^": operator.pow}
self.fn = {"sin": math.sin,
"cos": math.cos,
"tan": math.tan,
"exp": math.exp,
"abs": abs,
"trunc": lambda a: int(a),
"round": round,
"sgn": lambda a: abs(a) > epsilon and cmp(a, 0) or 0}
def evaluateStack(self, s):
op = s.pop()
if op == 'unary -':
return -self.evaluateStack(s)
if op in "+-*/^":
op2 = self.evaluateStack(s)
op1 = self.evaluateStack(s)
return self.opn[op](op1, op2)
elif op == "PI":
return math.pi # 3.1415926535
elif op == "E":
return math.e # 2.718281828
elif op in self.fn:
return self.fn[op](self.evaluateStack(s))
elif op[0].isalpha():
return 0
else:
return float(op)
def eval(self, num_string, parseAll=True):
self.exprStack = []
results = self.bnf.parseString(num_string, parseAll)
val = self.evaluateStack(self.exprStack[:])
return val
你可以像这样使用它
nsp = NumericStringParser()
result = nsp.eval('2^4')
print(result)
# 16.0
result = nsp.eval('exp(2^4)')
print(result)
# 8886110.520507872
解决方案 3:
eval()
和*的一些更安全的替代方案:sympy.sympify().evalf()
阿斯特瓦尔
数字表达式
*sympify
根据文档中的以下警告,SymPy也不安全。
警告:请注意,此函数使用
eval
,因此不应在未清理的输入上使用。
解决方案 4:
eval
和如此危险的原因exec
是默认compile
函数将为任何有效的 Python 表达式生成字节码,而默认eval
或exec
将执行任何有效的 Python 字节码。迄今为止,所有答案都集中在限制可以生成的字节码(通过清理输入)或使用 AST 构建您自己的域特定语言。
相反,您可以轻松创建一个简单的eval
函数,它无法执行任何恶意操作,并且可以轻松对内存或使用时间进行运行时检查。当然,如果是简单的数学运算,那么还有一条捷径。
c = compile(stringExp, 'userinput', 'eval')
if c.co_code[0]==b'd' and c.co_code[3]==b'S':
return c.co_consts[ord(c.co_code[1])+ord(c.co_code[2])*256]
其工作原理很简单,任何常量数学表达式在编译期间都会被安全地求值并存储为常量。 compile 返回的代码对象由 (d
即 的字节码)组成LOAD_CONST
,后跟要加载的常量的编号(通常是列表中的最后一个),后跟S
(即 的字节码RETURN_VALUE
)。 如果此快捷方式不起作用,则意味着用户输入不是常量表达式(包含变量或函数调用或类似内容)。
这也为一些更复杂的输入格式打开了大门。例如:
stringExp = "1 + cos(2)"
这需要实际评估字节码,这仍然非常简单。Python 字节码是一种面向堆栈的语言,因此一切都很简单TOS=stack.pop(); op(TOS); stack.put(TOS)
或类似。关键是只实现安全的操作码(加载/存储值、数学运算、返回值)而不是不安全的操作码(属性查找)。如果您希望用户能够调用函数(不使用上述快捷方式的全部原因),只需让您的实现CALL_FUNCTION
仅允许“安全”列表中的函数即可。
from dis import opmap
from Queue import LifoQueue
from math import sin,cos
import operator
globs = {'sin':sin, 'cos':cos}
safe = globs.values()
stack = LifoQueue()
class BINARY(object):
def __init__(self, operator):
self.op=operator
def __call__(self, context):
stack.put(self.op(stack.get(),stack.get()))
class UNARY(object):
def __init__(self, operator):
self.op=operator
def __call__(self, context):
stack.put(self.op(stack.get()))
def CALL_FUNCTION(context, arg):
argc = arg[0]+arg[1]*256
args = [stack.get() for i in range(argc)]
func = stack.get()
if func not in safe:
raise TypeError("Function %r now allowed"%func)
stack.put(func(*args))
def LOAD_CONST(context, arg):
cons = arg[0]+arg[1]*256
stack.put(context['code'].co_consts[cons])
def LOAD_NAME(context, arg):
name_num = arg[0]+arg[1]*256
name = context['code'].co_names[name_num]
if name in context['locals']:
stack.put(context['locals'][name])
else:
stack.put(context['globals'][name])
def RETURN_VALUE(context):
return stack.get()
opfuncs = {
opmap['BINARY_ADD']: BINARY(operator.add),
opmap['UNARY_INVERT']: UNARY(operator.invert),
opmap['CALL_FUNCTION']: CALL_FUNCTION,
opmap['LOAD_CONST']: LOAD_CONST,
opmap['LOAD_NAME']: LOAD_NAME
opmap['RETURN_VALUE']: RETURN_VALUE,
}
def VMeval(c):
context = dict(locals={}, globals=globs, code=c)
bci = iter(c.co_code)
for bytecode in bci:
func = opfuncs[ord(bytecode)]
if func.func_code.co_argcount==1:
ret = func(context)
else:
args = ord(bci.next()), ord(bci.next())
ret = func(context, args)
if ret:
return ret
def evaluate(expr):
return VMeval(compile(expr, 'userinput', 'eval'))
显然,实际版本会更长一些(有 119 个操作码,其中 24 个与数学有关)。添加STORE_FAST
和其他几个操作码可以非常轻松地输入类似'x=5;return x+x
或相似的内容。它甚至可以用于执行用户创建的函数,只要用户创建的函数本身是通过 VMeval 执行的(不要让它们可调用!!!否则它们可能会在某个地方被用作回调)。处理循环需要支持字节码,这意味着从迭代器goto
更改为当前指令并维护指向当前指令的指针,但这并不难。为了抵抗 DOS,主循环应该检查自计算开始以来已经过了多少时间,并且某些运算符应该拒绝超过某个合理限制的输入(这是最明显的)。for
`while`BINARY_POWER
虽然这种方法比简单表达式的简单语法解析器要长一些(参见上文关于仅获取编译常量的介绍),但它可以轻松扩展到更复杂的输入,并且不需要处理语法(compile
将任何任意复杂的东西简化为一系列简单的指令)。
解决方案 5:
好的,eval 的问题在于它可以很容易地逃离沙箱,即使你摆脱了__builtins__
。逃离沙箱的所有方法都归结为使用getattr
或object.__getattribute__
(通过.
运算符)通过某个允许的对象(或类似对象)获取对某个危险对象的引用''.__class__.__bases__[0].__subclasses__
。 通过将 设置为 来getattr
消除。 是困难的,因为它不能简单地被删除,因为它是不可变的,并且删除它会破坏所有内容。但是,只能通过运算符访问,因此从输入中清除它足以确保 eval 无法逃离其沙箱。
在处理公式时,小数的唯一有效用途是当它前面或后面是 时,因此我们只需删除 的所有其他实例。__builtins__
`Noneobject.__getattribute__
object__getattribute__
.` [0-9]
`.`
import re
inp = re.sub(r".(?![0-9])","", inp)
val = eval(inp, {'__builtins__':None})
请注意,虽然 python 通常将 视为1 + 1.
,1 + 1.0
但这将删除尾随部分.
,只留下1 + 1
。您可以将)
、和添加EOF
到允许跟在 后面的事物列表中.
,但何必呢?
解决方案 6:
您可以使用 ast 模块并编写一个 NodeVisitor 来验证每个节点的类型是否是白名单的一部分。
import ast, math
locals = {key: value for (key,value) in vars(math).items() if key[0] != '_'}
locals.update({"abs": abs, "complex": complex, "min": min, "max": max, "pow": pow, "round": round})
class Visitor(ast.NodeVisitor):
def visit(self, node):
if not isinstance(node, self.whitelist):
raise ValueError(node)
return super().visit(node)
whitelist = (ast.Module, ast.Expr, ast.Load, ast.Expression, ast.Add, ast.Sub, ast.UnaryOp, ast.Num, ast.BinOp,
ast.Mult, ast.Div, ast.Pow, ast.BitOr, ast.BitAnd, ast.BitXor, ast.USub, ast.UAdd, ast.FloorDiv, ast.Mod,
ast.LShift, ast.RShift, ast.Invert, ast.Call, ast.Name)
def evaluate(expr, locals = {}):
if any(elem in expr for elem in '
#') : raise ValueError(expr)
try:
node = ast.parse(expr.strip(), mode='eval')
Visitor().visit(node)
return eval(compile(node, "<string>", "eval"), {'__builtins__': None}, locals)
except Exception: raise ValueError(expr)
由于它通过白名单而不是黑名单工作,因此它是安全的。它唯一可以访问的函数和变量是您明确授予其访问权限的函数和变量。我在一个字典中填充了与数学相关的函数,因此您可以根据需要轻松提供对这些函数的访问权限,但您必须明确使用它。
如果字符串尝试调用未提供的函数或调用任何方法,则会引发异常,并且不会执行。
因为它使用了 Python 内置的解析器和评估器,所以它也继承了 Python 的优先级和提升规则。
>>> evaluate("7 + 9 * (2 << 2)")
79
>>> evaluate("6 // 2 + 0.0")
3.0
上述代码仅在 Python 3 上测试过。
如果需要,您可以在此功能上添加超时装饰器。
解决方案 7:
基于Perkins 的惊人方法,我更新并改进了他的简单代数表达式(无函数或变量)的“快捷方式”。现在它可以在 Python 3.6+ 上运行,并避免了一些陷阱:
import re
# Kept outside simple_eval() just for performance
_re_simple_eval = re.compile(rb'd([x00-xFF]+)Sx00')
def simple_eval(expr):
try:
c = compile(expr, 'userinput', 'eval')
except SyntaxError:
raise ValueError(f"Malformed expression: {expr}")
m = _re_simple_eval.fullmatch(c.co_code)
if not m:
raise ValueError(f"Not a simple algebraic expression: {expr}")
try:
return c.co_consts[int.from_bytes(m.group(1), sys.byteorder)]
except IndexError:
raise ValueError(f"Expression not evaluated as constant: {expr}")
测试,使用其他答案中的一些示例:
for expr, res in (
('2^4', 6 ),
('2**4', 16 ),
('1 + 2*3**(4^5) / (6 + -7)', -5.0 ),
('7 + 9 * (2 << 2)', 79 ),
('6 // 2 + 0.0', 3.0 ),
('2+3', 5 ),
('6+4/2*2', 10.0 ),
('3+2.45/8', 3.30625),
('3**3*3/3+3', 30.0 ),
):
result = simple_eval(expr)
ok = (result == res and type(result) == type(res))
print("{} {} = {}".format("OK!" if ok else "FAIL!", expr, result))
OK! 2^4 = 6
OK! 2**4 = 16
OK! 1 + 2*3**(4^5) / (6 + -7) = -5.0
OK! 7 + 9 * (2 << 2) = 79
OK! 6 // 2 + 0.0 = 3.0
OK! 2+3 = 5
OK! 6+4/2*2 = 10.0
OK! 3+2.45/8 = 3.30625
OK! 3**3*3/3+3 = 30.0
测试错误输入:
for expr in (
'foo bar',
'print("hi")',
'2*x',
'lambda: 10',
'2**1234',
):
try:
result = simple_eval(expr)
except ValueError as e:
print(e)
continue
print("OK!") # will never happen
Malformed expression: foo bar
Not a simple algebraic expression: print("hi")
Expression not evaluated as constant: 2*x
Expression not evaluated as constant: lambda: 10
Expression not evaluated as constant: 2**1234
解决方案 8:
我想我会使用eval()
,但首先要检查以确保字符串是有效的数学表达式,而不是恶意的。您可以使用正则表达式进行验证。
eval()
还采用了额外的参数,您可以使用这些参数来限制其操作的命名空间,以提高安全性。
解决方案 9:
[我知道这是一个老问题,但当出现新的有用的解决方案时,值得指出]
从 python3.6 开始,此功能已内置到语言中,称为“f 字符串”。
参见:PEP 498——文字字符串插值
例如(注意f
前缀):
f'{2**4}'
=> '16'
解决方案 10:
这是一个非常迟的回复,但我认为对将来的参考很有用。您可以使用 SymPy,而不是编写自己的数学解析器(尽管上面的 pyparsing 示例很棒)。我对它没有太多经验,但它包含一个比任何人可能为特定应用程序编写的更强大的数学引擎,并且基本表达式求值非常简单:
>>> import sympy
>>> x, y, z = sympy.symbols('x y z')
>>> sympy.sympify("x**3 + sin(y)").evalf(subs={x:1, y:-3})
0.858879991940133
确实很酷!Afrom sympy import *
带来了更多函数支持,例如三角函数、特殊函数等,但我在这里避免使用它们,以展示它们来自哪里。
解决方案 11:
使用 lark 解析器库
from operator import add, sub, mul, truediv, neg, pow
from lark import Lark, Transformer, v_args
calc_grammar = f"""
?start: sum
?sum: product
| sum "+" product -> {add.__name__}
| sum "-" product -> {sub.__name__}
?product: power
| product "*" power -> {mul.__name__}
| product "/" power -> {truediv.__name__}
?power: atom
| power "^" atom -> {pow.__name__}
?atom: NUMBER -> number
| "-" atom -> {neg.__name__}
| "(" sum ")"
%import common.NUMBER
%import common.WS_INLINE
%ignore WS_INLINE
"""
@v_args(inline=True)
class CalculateTree(Transformer):
add = add
sub = sub
neg = neg
mul = mul
truediv = truediv
pow = pow
number = float
calc_parser = Lark(calc_grammar, parser="lalr", transformer=CalculateTree())
calc = calc_parser.parse
def eval_expr(expression: str) -> float:
return calc(expression)
print(eval_expr("2^4"))
print(eval_expr("-1*2^4"))
print(eval_expr("-2^3 + 1"))
print(eval_expr("2**4")) # Error
解决方案 12:
eval
在干净的命名空间中使用:
>>> ns = {'__builtins__': None}
>>> eval('2 ** 4', ns)
16
干净的命名空间应该可以防止注入。例如:
>>> eval('__builtins__.__import__("os").system("echo got through")', ns)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute '__import__'
否则你会得到:
>>> eval('__builtins__.__import__("os").system("echo got through")')
got through
0
您可能想要授予对数学模块的访问权限:
>>> import math
>>> ns = vars(math).copy()
>>> ns['__builtins__'] = None
>>> eval('cos(pi/3)', ns)
0.50000000000000011
解决方案 13:
这是我不使用 eval 来解决问题的方法。适用于 Python2 和 Python3。它不适用于负数。
$ python -m pytest test.py
测试.py
from solution import Solutions
class SolutionsTestCase(unittest.TestCase):
def setUp(self):
self.solutions = Solutions()
def test_evaluate(self):
expressions = [
'2+3=5',
'6+4/2*2=10',
'3+2.45/8=3.30625',
'3**3*3/3+3=30',
'2^4=6'
]
results = [x.split('=')[1] for x in expressions]
for e in range(len(expressions)):
if '.' in results[e]:
results[e] = float(results[e])
else:
results[e] = int(results[e])
self.assertEqual(
results[e],
self.solutions.evaluate(expressions[e])
)
解决方案.py
class Solutions(object):
def evaluate(self, exp):
def format(res):
if '.' in res:
try:
res = float(res)
except ValueError:
pass
else:
try:
res = int(res)
except ValueError:
pass
return res
def splitter(item, op):
mul = item.split(op)
if len(mul) == 2:
for x in ['^', '*', '/', '+', '-']:
if x in mul[0]:
mul = [mul[0].split(x)[1], mul[1]]
if x in mul[1]:
mul = [mul[0], mul[1].split(x)[0]]
elif len(mul) > 2:
pass
else:
pass
for x in range(len(mul)):
mul[x] = format(mul[x])
return mul
exp = exp.replace(' ', '')
if '=' in exp:
res = exp.split('=')[1]
res = format(res)
exp = exp.replace('=%s' % res, '')
while '^' in exp:
if '^' in exp:
itm = splitter(exp, '^')
res = itm[0] ^ itm[1]
exp = exp.replace('%s^%s' % (str(itm[0]), str(itm[1])), str(res))
while '**' in exp:
if '**' in exp:
itm = splitter(exp, '**')
res = itm[0] ** itm[1]
exp = exp.replace('%s**%s' % (str(itm[0]), str(itm[1])), str(res))
while '/' in exp:
if '/' in exp:
itm = splitter(exp, '/')
res = itm[0] / itm[1]
exp = exp.replace('%s/%s' % (str(itm[0]), str(itm[1])), str(res))
while '*' in exp:
if '*' in exp:
itm = splitter(exp, '*')
res = itm[0] * itm[1]
exp = exp.replace('%s*%s' % (str(itm[0]), str(itm[1])), str(res))
while '+' in exp:
if '+' in exp:
itm = splitter(exp, '+')
res = itm[0] + itm[1]
exp = exp.replace('%s+%s' % (str(itm[0]), str(itm[1])), str(res))
while '-' in exp:
if '-' in exp:
itm = splitter(exp, '-')
res = itm[0] - itm[1]
exp = exp.replace('%s-%s' % (str(itm[0]), str(itm[1])), str(res))
return format(exp)
解决方案 14:
我来这里也是为了寻找数学表达式解析器。阅读一些答案并查找库后,我偶然发现了我现在正在使用的py-expression。它基本上可以处理很多运算符和公式构造,但如果您缺少某些东西,您可以轻松地向其中添加新的运算符/函数。
基本语法是:
from py_expression.core import Exp
exp = Exp()
parsed_formula = exp.parse('a+4')
result = exp.eval(parsed_formula, {"a":2})
到目前为止,我遇到的唯一问题是它没有内置数学常数,也没有添加它们的机制。我只是提出了一个解决方案:https://github.com/FlavioLionelRita/py-expression/issues/7
解决方案 15:
如何实现针对特定问题量身定制的表达式求解器?使用scinumtools 表达式求解器,您可以使用预先存在的运算符,也可以从头开始创建自己的运算符。
from scinumtools.solver import *
with ExpressionSolver(AtomBase) as es:
es.solve("sin(23) < 1 && 3*2 == 6 || !(23 > 43) && cos(0) == 1")
这是一个关于如何构建您自己的操作符的小示例:
class OperatorSquare(OperatorBase): # operate from left side
symbol: str = '~'
def operate_unary(self, tokens):
right = tokens.get_right()
tokens.put_left(right*right)
class OperatorCube(OperatorBase): # operate from right side
symbol: str = '^'
def operate_unary(self, tokens):
left = tokens.get_left()
tokens.put_left(left*left*left)
operators ={'square':OperatorSquare,'cube':OperatorCube,'add':OperatorAdd}
steps = [
dict(operators=['square','cube'], otype=Otype.UNARY),
dict(operators=['add'], otype=Otype.BINARY),
]
with ExpressionSolver(AtomBase, operators, steps) as es:
es.solve('~3 + 2^')
# will result in: Atom(17)
你甚至可以改变原子的行为:
class AtomCustom(AtomBase):
value: str
def __init__(self, value:str):
self.value = str(value)
def __add__(self, other):
return AtomCustom(self.value + other.value)
def __gt__(self, other):
return AtomCustom(len(self.value) > len(other.value))
operators = {'add':OperatorAdd,'gt':OperatorGt,'par':OperatorPar}
steps = [
dict(operators=['par'], otype=Otype.ARGS),
dict(operators=['add'], otype=Otype.BINARY),
dict(operators=['gt'], otype=Otype.BINARY),
]
with ExpressionSolver(AtomCustom, operators, steps) as es:
es.solve("(limit + 100 km/s) > (limit + 50000000000 km/s)")
# will result in: Atom('False')
顺便说一句,我是作者,我非常有兴趣听到你对scinumtools 项目的评论和建议。请在GitHub或PyPi上查看
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件