Seite 1 von 1

mathematische ausdruecke evaluieren

Verfasst: Mittwoch 26. März 2008, 01:55
von Costi
ich will mathematische ausdruecke (zb 1+5*(4-3)) evaluieren
eval() kommt aus sicherheitsgruenden nicht in frage


irgendwelche ideen?

Verfasst: Mittwoch 26. März 2008, 02:08
von EyDu
Einen einfachen kleinen Parser. Ist warscheinlich bei einigen Parsertools schon als Sample zu finden.

Die Suche liefert dazu bestimmt einiges.

Und sonst ist ein

Code: Alles auswählen

eval(expr, {}, {})
auch ganz einfach.

Verfasst: Mittwoch 26. März 2008, 09:33
von schorsch
EyDu hat geschrieben:Und sonst ist ein

Code: Alles auswählen

eval(expr, {}, {})
auch ganz einfach.
damit sind allerdings die builtin-funktionen immernoch verfügbar.
abhilfe schafft da

Code: Alles auswählen

eval(expr, {"__builtins__": None})

Verfasst: Mittwoch 26. März 2008, 09:54
von Hyperion
Wobei ein echter Parser wesentlich flexibler ist ;-)

Verfasst: Mittwoch 26. März 2008, 11:05
von sma
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

Verfasst: Mittwoch 26. März 2008, 23:08
von Costi
als minimalist habe ich folgende loesung vorgezogen:

Code: Alles auswählen

In [16]: import math

In [17]: namespace = {'__builtins__': None}

In [18]: namespace.update(math.__dict__)

In [19]: eval('8*cos(5)', namespace, {})
Out[19]: 2.26929748370581

@sma
danke!, wenn ich mal zeit habe schreibe ich alles basierend an deiner loesung um :wink:

EDIT:
bug gefixt (siehe nachster post)

Verfasst: Mittwoch 26. März 2008, 23:21
von Trundle

Code: Alles auswählen

__import__('os').system('touch ev1l')
scheint damit aber noch zu funktionieren.

Verfasst: Mittwoch 26. März 2008, 23:34
von schorsch
__builtins__ muss in anführungszeichen gesetzt werden dann klappts.

Verfasst: Donnerstag 27. März 2008, 00:31
von Costi
aua!!

da sieht man was fuer schwere auswirkungen ein kleiner fehler haben kann......

Verfasst: Donnerstag 27. März 2008, 09:38
von birkenfeld
Und

Code: Alles auswählen

''.__class__.__bases__[0].__bases__[0].__subclasses__()[20]('/etc/passwd', 'r').read()
funktioniert damit auch immer noch.
(Die "20" ist natürlich Interpreter-spezifisch.)

Verfasst: Donnerstag 27. März 2008, 12:41
von Trundle
birkenfeld hat geschrieben:(Die "20" ist natürlich Interpreter-spezifisch.)
Was ja aber auch kein Hindernis ist.

Verfasst: Donnerstag 27. März 2008, 16:15
von Costi
nicht schlecht (-;

dann komme ich um einem parser wohl nicht herum....
einfach die eingabe zu ueberpruefen, bevor sie eval uebergeben wird ist mir zu riskant

Verfasst: Donnerstag 27. März 2008, 16:28
von jens
Und wie wäre es wenn man vorher prüft ob nur Zahlen und "+-*/()" vorkommen?

EDIT: OK, wenn natürlich auch sowas wie "cos" usw. vorkommen können, dann wird es wieder haarig... Naja, man könnte natürlich alle Strings erlauben, die eine math Funktion sind...

Verfasst: Donnerstag 27. März 2008, 16:34
von audax
Warum nicht einfach nen parser bauen, ders ordentlich validiert?

Verfasst: Donnerstag 27. März 2008, 16:37
von jens
Naja, wenn man eh alles zerkleinern muß, dann liegt es wohl nahe ganz auf eval zu verzichten und die Lösung von sma sich näher anzusehen ;)