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

mathematische ausdruecke evaluieren

Beitragvon Costi » Mittwoch 26. März 2008, 01:55

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: 4866
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Beitragvon EyDu » Mittwoch 26. März 2008, 02:08

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

Beitragvon schorsch » Mittwoch 26. März 2008, 09:33

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: 7471
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Beitragvon Hyperion » Mittwoch 26. März 2008, 09:54

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

Beitragvon sma » Mittwoch 26. März 2008, 11:05

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: 544
Registriert: Donnerstag 17. August 2006, 14:21

Beitragvon Costi » Mittwoch 26. März 2008, 23:08

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

Beitragvon Trundle » Mittwoch 26. März 2008, 23:21

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

Beitragvon schorsch » Mittwoch 26. März 2008, 23:34

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

Beitragvon Costi » Donnerstag 27. März 2008, 00:31

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

Beitragvon birkenfeld » Donnerstag 27. März 2008, 09:38

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

Beitragvon Trundle » Donnerstag 27. März 2008, 12:41

birkenfeld hat geschrieben:(Die "20" ist natürlich Interpreter-spezifisch.)

Was ja aber auch kein Hindernis ist.
Costi
User
Beiträge: 544
Registriert: Donnerstag 17. August 2006, 14:21

Beitragvon Costi » Donnerstag 27. März 2008, 16:15

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
Moderator
Beiträge: 8458
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Beitragvon jens » Donnerstag 27. März 2008, 16:28

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

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
audax
User
Beiträge: 830
Registriert: Mittwoch 19. Dezember 2007, 10:38

Beitragvon audax » Donnerstag 27. März 2008, 16:34

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

Beitragvon jens » Donnerstag 27. März 2008, 16:37

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

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder