mathematische ausdruecke evaluieren

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Costi
User
Beiträge: 545
Registriert: Donnerstag 17. August 2006, 14:21

ich will mathematische ausdruecke (zb 1+5*(4-3)) evaluieren
eval() kommt aus sicherheitsgruenden nicht in frage


irgendwelche ideen?
cp != mv
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

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.
schorsch
User
Beiträge: 18
Registriert: Montag 26. November 2007, 18:39

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})
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Wobei ein echter Parser wesentlich flexibler ist ;-)
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

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
Costi
User
Beiträge: 545
Registriert: Donnerstag 17. August 2006, 14:21

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)
Zuletzt geändert von Costi am Donnerstag 27. März 2008, 00:29, insgesamt 1-mal geändert.
cp != mv
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

Code: Alles auswählen

__import__('os').system('touch ev1l')
scheint damit aber noch zu funktionieren.
schorsch
User
Beiträge: 18
Registriert: Montag 26. November 2007, 18:39

__builtins__ muss in anführungszeichen gesetzt werden dann klappts.
Costi
User
Beiträge: 545
Registriert: Donnerstag 17. August 2006, 14:21

aua!!

da sieht man was fuer schwere auswirkungen ein kleiner fehler haben kann......
cp != mv
Benutzeravatar
birkenfeld
Python-Forum Veteran
Beiträge: 1603
Registriert: Montag 20. März 2006, 15:29
Wohnort: Die aufstrebende Universitätsstadt bei München

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.)
Dann lieber noch Vim 7 als Windows 7.

http://pythonic.pocoo.org/
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

birkenfeld hat geschrieben:(Die "20" ist natürlich Interpreter-spezifisch.)
Was ja aber auch kein Hindernis ist.
Costi
User
Beiträge: 545
Registriert: Donnerstag 17. August 2006, 14:21

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
cp != mv
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

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...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
audax
User
Beiträge: 830
Registriert: Mittwoch 19. Dezember 2007, 10:38

Warum nicht einfach nen parser bauen, ders ordentlich validiert?
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

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 ;)

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Antworten