Costi, wenn dir `eval()` unsicher scheint, könntest du den String vorher durch eine Parser schicken und dich so von der Gültigkeit des Ausdrucks überzeugen. Danach könntest du `eval()` immer noch benutzen. Oder du baust den Parser zu einem Interpreter um. Pythons eigener Interpreter (und Bytecode-Compiler) wird aber wohl effizienter sein.
Da ich gerade Spaß an Kombinatorparsern (und kurzen Variablennamen) habe, biete ich folgenden Quelltext (ohne Gewähr als Inspirationsquelle) an:
Code: Alles auswählen
class parser:
def action(self, f): self.f = f; return self
def ignore(self): return self.action(lambda v: None)
class word(parser):
def __init__(self, p): self.p, self.f = "\\s*(%s)" % p, lambda r: r
def __call__(self, s):
m = re.match(self.p, s)
if m: return (self.f(m.group(1)), s[m.end(1):])
def token(t):
return word(re.escape(t)).ignore()
class seq(parser):
def __init__(self, *ps): self.ps, self.f = self.t(ps), lambda r: r[0]
def __call__(self, s):
rs = []
for p in self.ps:
r = p(s)
if not r: return None
if r[0] is not None: rs.append(r[0])
s = r[1]
return (self.f(rs), s)
def t(self, ps): return [token(p) if isinstance(p, str) else p for p in ps]
class alt(parser):
def __init__(self, *ps): self.ps, self.f = ps, lambda r: r
def __call__(self, s):
for p in self.ps:
r = p(s)
if r: return r
class rep(parser):
def __init__(self, *ps): self.p, self.f = seq(*ps), lambda r: r
def __call__(self, s):
rs = []
while True:
r = self.p(s)
if not r: return (self.f(rs), s)
rs.append(r[0])
s = r[1]
class forward:
def __lshift__(self, p): self.p = p
def __call__(self, s): return self.p(s)
Damit kann man fast wie eine Grammatik im EBNF-Format einen Parser für einfache mathematische Ausdrücke definieren:
Code: Alles auswählen
expression = forward()
number = word("\\d+")
primary = alt(number, seq("(", expression, ")"))
factor = alt(seq("-", primary), seq("+", primary), primary)
term = alt(seq(factor, rep("*", factor)), seq(factor, rep("/", factor)), factor)
expression << alt(seq(term, rep("+", term)), seq(term, rep("-", term)), term)
wenn `expression("3+4")[1] == ''` gilt, wird die komplette Eingabe "3+4" vom Parser verstanden und entspricht folglich der Grammatik.
Da meine Parser erlauben, Aktionen für erkannte Fragmente auszuführen, kann man den Wert auch gleich berechnen:
Code: Alles auswählen
def fold(f, e, l):
return fold(f, f(e, l[0]), l[1:]) if l else e
expression = forward()
number = word("\\d+").action(lambda r: int(r))
primary = alt(number, seq("(", expression, ")"))
factor = alt(
seq("-", primary).action(lambda r: -r[0]),
seq("+", primary).action(lambda r: +r[0]),
primary)
term = alt(
seq(factor, rep("*", factor)).action(lambda r: fold(int.__mul__, r[0], r[1])),
seq(factor, rep("/", factor)).action(lambda r: fold(int.__div__, r[0], r[1])),
factor)
expression << alt(
seq(term, rep("+", term)).action(lambda r: fold(int.__add__, r[0], r[1])),
seq(term, rep("-", term)).action(lambda r: fold(int.__sub__, r[0], r[1])),
term)
print expression("1 + 2 * 3")
Stefan