Übergabe/parsen von komplexen Funktionen als Parameter

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
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

Das Thema ist trifft es nicht so ganz.
Man stelle sich vor, man möchte beispielsweise ein Programm schreiben, das Funktionsverläufe in einem Koordinatensystem grafisch darstellt.

Dann würde irgendwo im Quelltext stehen

Code: Alles auswählen

Koordinatensystem.zeichne(math.sin)
Soweit, so gut. Wenn ich aber eine komplexere Funktion übergeben möchte, die vielleicht auch erst zur Laufzeit eingegeben wird, welche Möglichkeiten habe ich dann?

Code: Alles auswählen

def my_complex_function(x):
    return x*math.sin(x)

Koordinatensystem.zeichne(my_complex_function) #das funktioniert

#kann man aber auch irgendwie so etwas schreiben:
Koordinatensystem.zeichne(def dummy: return x*math.sin(x))

#oder noch einer oben drauf
funktionsstring = input("Gib eine Funktion ein, die ich zeichnen soll:")
Koordinatensystem.zeichne(parse_string(funktionsstring))
Ich will mich ja nun ein wenig mit Eurer Schlange beschäftigen und da krabbelt durch mein Gehirn so etwas, wie ein keyboard/notenabspieler fürs Tablet. Dabei kommen dann die Vorzüge von python m.E. zum Tragen, ich kann entweder eine Note übergeben, oder aber auch eine Liste von Noten. Und diese könnten auch von unterschiedlichem Klang sein etc. Sicher geht das in anderen Sprachen auch, wenn man die Vererbungshierarchie richtig aufbaut und Funktionen überlädt. Aber dann ist ja der Übungseffekt weg :)
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Das ganze klappt nur nicht, weil es syntaktisch falsch ist ;)

Das Stichwort hier sind `lambda`s/anonyme Funktionen:

Code: Alles auswählen

Koordinatensystem.zeichne(lambda x: x*math.sin(x))
BlackJack

@NoPy: Man könnte bei Benutzereingaben mit `compile()` und `exec` arbeiten. Wobei man sich um die Sicherheitslücken bewusst sein muss, die man sich damit reinholt, dass der Benutzer nahezu beliebigen Code ausführen lassen kann. Das kann man eigentlich nur durch einen eigenen Parser für mathematische Ausdrücke sicherstellen, zum Überprüfen der Eingabe bevor man das ausführt.
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

@cofi: geht klar, klingt plausibel, beschäftige ich mich mit
@BlackJack: das mit den Sicherheitslücken ist klar, aber wie müsste der Code aussehen? In meinem Hinterkopf bildet sich gerade so etwas, wie: Probier ich doch mal aus, wie (math.sin(x)+math.sin(3*x)/3)/(1000+x)) sich anhört, und da würde ich gern nicht erst im Quelltext ändern. Das eine fertige Anwendung weniger Freiheitsgrade bekommen würde, ist schon klar. Wobei ich nicht weiß, ob es in diesem Sinne je diese "fertige" Anwendung geben wird. Es ist halt ein Schnupperprojekt.
BlackJack

Code: Alles auswählen

In [23]: formula = 'y=(math.sin(x)+math.sin(3*x)/3)/(1000+x)'

In [24]: code = compile(formula, '<input>', 'exec')

In [25]: environment = {'x': 42, '__builtins__': None, 'math': math}
In [26]: exec code in environment

In [27]: environment
Out[27]: 
{'__builtins__': None,
 'math': <module 'math' (built-in)>,
 'x': 42,
 'y': -0.0007740159366836594}
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

BlackJack hat geschrieben:

Code: Alles auswählen

In [23]: formula = 'y=(math.sin(x)+math.sin(3*x)/3)/(1000+x)' #verstanden

In [24]: code = compile(formula, '<input>', 'exec') #schon wieder schlechter, was bedeuten jetzt '<input>' und 'exec'

In [25]: environment = {'x': 42, '__builtins__': None, 'math': math} #Das ist ein Dictionary mit dem Namen environment und dem Inhalt x=42, __builtins__ = None (was auch immer das bedeutet) und 'math' = math, also die Mathematikbibliothek findet man hier
In [26]: exec code in environment # code ist irgend ein Compilat, environment ein Dictionary, wobei mir schon mal nicht klar ist, woher er weiß, dass x ein Variablenname ist

In [27]: environment
Out[27]: 
{'__builtins__': None,
 'math': <module 'math' (built-in)>,
 'x': 42,
 'y': -0.0007740159366836594}
# und das ist auch nicht klarer. Gut, er hat x gefunden, durch 42 ersetzt und das Ergebnis in y geschrieben. Wenn ich aber 'y' auf -0,00077401 .. gesetzt hätte, hätte er dann auch ALLE x gefunden, bei denen die Aussage oben wieder wahr ist? Ist das "list comprehension"?
Also ganz ehrlich, dass benötigt eine Erklärung, sonst sterbe ich so dumm, wie ich bin ...
BlackJack

@NoPy: Du weisst schon das es Dokumentation gibt wo für Funktionen wie `compile()` steht was die machen und was für Argumente man übergeben kann‽ Und auch ``exec`` wird da beschrieben.

Das mit dem `__builtins__` ist vielleicht erklärungsbedürftig: Damit verhindert man das beim ``exec`` am Anfang Die „eingebauten” Objekte hinzugefügt werden, also alle Namen auf die man sonst so zurückgreifen kann ohne sie extra importieren zu müssen. Das ist sozusagen die Sicherheitsmasnahme dagegen wenn jemand als `formula` etwas wie '__import__("os").system("rm -rf ~")' eingibt, weil `__import__` dann nicht definiert ist.

Ansonsten verstehe ich die Fragen nicht so ganz, also woher weiss *wer* das `x` eine Variable ist? Wenn man in Python-Quelltext bei `formula` das `x` so hinschreibt dann ist das halt ein Name der zur Laufzeit zu einem Wert aufgelöst wird. Das ist so in der Sprachspezifikation festgelegt. Python muss dann nachschauen auf was die Zeichenkette 'x' abgebildet wird. In vielen Fällen steht das in irgendeinem Dictionary. Und nun schau Dir [26] an und rate einfach mal welches Dictionary das ist. Kannst auch in die Dokumentation schauen. :-)

Und Umgekehrt muss eine Zuweisung auch irgendwo als Abbildung von Name auf Wert gespeichert werden.

Die Vermutungen zu [27] sind ja total abstrus. Wie sollte das denn funktionieren? Python müsste dann zu *beliebigen* Code den man zum Beispiel auf Modulebene schreiben kann eine Art Umkehr*code* finden der genau das Gegenteil macht und darauf hin irgendwie rückwärts alle möglichen Werte ermittelt die zu einem bestimmten Wert hätten führen können und die dann… ja was eigentlich?
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

BlackJack hat geschrieben:@NoPy: Du weisst schon das es Dokumentation gibt wo für Funktionen wie `compile()` steht was die machen und was für Argumente man übergeben kann‽ Und auch ``exec`` wird da beschrieben.
Nun ja, bislang hat mich die Dokumentation von python nicht wirklich begeistert. Vielleicht fehlen mir die Zusammenhänge, aber ohne Beispiel konnte ich allein mit der Doku (von den grundlegenden Dingen mal abgesehen, wie Tupel, strings und co) nie arbeiten. Siehe meine "dumme" Frage zum Thema reguläre Ausdrücke. An sich völlig klar, aber man muss es sich denken, geschrieben steht es nirgends so richtig (oder ich bin zu ...)
Und konkret zur compile- Funktion: Du meinst diese Doku? http://docs.python.org/2/library/compiler.html
compile habe ich gefunden, zu formula finde ich nichts, was passt ...
BlackJack hat geschrieben:Das mit dem `__builtins__` ist vielleicht erklärungsbedürftig: Damit verhindert man das beim ``exec`` am Anfang Die „eingebauten” Objekte hinzugefügt werden, also alle Namen auf die man sonst so zurückgreifen kann ohne sie extra importieren zu müssen. Das ist sozusagen die Sicherheitsmasnahme dagegen wenn jemand als `formula` etwas wie '__import__("os").system("rm -rf ~")' eingibt, weil `__import__` dann nicht definiert ist.
tut mir leid, auch noch nicht klarer (und auch hier: kann in der doku nichts finden, freue mich über einen Link)
BlackJack hat geschrieben:Ansonsten verstehe ich die Fragen nicht so ganz, also woher weiss *wer* das `x` eine Variable ist? Wenn man in Python-Quelltext bei `formula` das `x` so hinschreibt dann ist das halt ein Name der zur Laufzeit zu einem Wert aufgelöst wird. Das ist so in der Sprachspezifikation festgelegt. Python muss dann nachschauen auf was die Zeichenkette 'x' abgebildet wird. In vielen Fällen steht das in irgendeinem Dictionary. Und nun schau Dir [26] an und rate einfach mal welches Dictionary das ist. Kannst auch in die Dokumentation schauen. :-)
ICh weiß, klingt faul, ist es aber nicht. Link? Das Problem bei solch allgemeinen Bezeichnern, wie "exec" ist, dass man dann wieder so viele bekommt, dass man sich schwer tut, das richtige zu finden. Zum x:
Im Grunde steht in dem String x....y, Du sagst irgendwann in einem Dictionary x=42. Also ersetzt er x durch 42. Aber wieso weiß er beim compilieren, dass "math.sin" nicht in irgend einem dictionary steht? oder "y"? Wie gesagt, wenn ich eine Dokumentation zu "formula" finde, die ich verstehe, dann kann ich das vielleicht nachvollziehen. Aber im Moment kann ich nur raten.
BlackJack hat geschrieben: Und Umgekehrt muss eine Zuweisung auch irgendwo als Abbildung von Name auf Wert gespeichert werden.

Die Vermutungen zu [27] sind ja total abstrus. Wie sollte das denn funktionieren? Python müsste dann zu *beliebigen* Code den man zum Beispiel auf Modulebene schreiben kann eine Art Umkehr*code* finden der genau das Gegenteil macht und darauf hin irgendwie rückwärts alle möglichen Werte ermittelt die zu einem bestimmten Wert hätten führen können und die dann… ja was eigentlich?
Das hat mich schon auch gewundert, aber im Moment traue ich python alles zu ;) Ich hatte mir mal den englischen Artikel zur list comprehension hereingeholfen und soviel ich verstanden habe, wird dabei die aus dem Mathematikunterricht bekannte Mengenbeschreibung "codiert", also so etwas, wie die Menge aller x, für die gilt x ist Element der natürlichen Zahlen und x² - 17<212
Und dann wird über x im Bereich der natürlichen Zahlen iteriert (kann eine Weile dauern) und alle passenden x werden ausgeworfen. Und in der Mathematik kann ich auch definieren: gesucht ist die Menge aller x, für die gilt y=-0.0007...=sin(x)*x usw. Dass das nicht immer trivial umformbar ist, ist mir klar. Aber weiß ich denn, ob python nicht auch backtracking oder so etwas im Programm hat? Sicher, das Abbruchkriterium wäre interessant, aber vielleicht kann man ja auch das vorgeben: Wenn Du 1000 Werte hast, dann gibt Dich zufrieden.

Aktuell weiß ich nicht, was python alles kann und was nicht. Ich weiß nur, dass ich noch ganz am Anfang stehe.
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

NoPy hat geschrieben:
BlackJack hat geschrieben:Und konkret zur compile- Funktion: Du meinst diese Doku? http://docs.python.org/2/library/compiler.html
compile habe ich gefunden, zu formula finde ich nichts, was passt ...
Nein das ist die Doku zum compiler-Paket. Die Doku hat eine Suchfunktion. http://docs.python.org/2.7/library/func ... le#compile ist der 10. Treffer oder so wenn man nach compile sucht… Im ersten Satz ist sogar exec verlinkt.

PS: Mit compile function python ist das sogar der erste Treffer bei google…
BlackJack

@NoPy: Wenn man in der Dokumentation nach etwas sucht von dem man nur den Namen kennt, dann ist der Index nützlich. Der Link dort hin ist fast auf jeder Dokumentationsseite oben rechts zu finden. Da dann 'c' auswählen und nach `compile` suchen. Da werden dann für die eingebaute Funktion `compile()` fünf Links in die Dokumentation aufgeführt. Wenn man den ersten klickt kommt man zwar nicht direkt zur Dokumentation der Funktion, aber sie wird dort erwähnt, und dann ist es in der Regel nur einen weiteren Klick entfernt, denn in aller Regel sind Namen von Dingen aus der Standardbibliothek innerhalb der Dokumentation Links auf die Dokumentation zu dem Ding mit dem Namen. Also dort einfach auf `compile()` klicken und schon landet man bei compile(). In dem Text steht das es was mit ``exec`` zu tun hat und das Wort ``exec`` dort ist ein Link zur Dokumentation von ``exec``.

Und warum hast Du nach `formula` gesucht? Das ist doch offensichtlich nichts aus der Standardbibliothek sondern ein Name der selbst definiert wird. Da gibt es keine Dokumentation zu, das hätte man auch `parrot` oder `katzenklo` nennen können. Da es den Quelltext einer Formel als Zeichenkette enthält habe ich mich halt für den Namen `formula` entschieden.

Beim kompilieren wird weder `x` noch `math.sin` durch einen Wert ersetzt, das passiert erst beim Ausführen mit ``exec``. Welches Dictionary als Namensraum bei der Ausführung dient wird *dort* ja angegeben. Und da wird dann halt nachgeschaut. Wenn im Code ein Name vorkommt der dort nicht vorkommt oder zu den eingebauten Namen gehört, dann gibt es zur Laufzeit von ``exec`` einen `NameError`. Und wenn in dem Code den ``exec`` ausführt ein Name an einen Wert gebunden wird, dann landet der auch in dem Dictionary. Entweder neu, wenn es ihn vorher nicht gab, oder der alte Wert wird ersetzt. Halt das was man so von einer Zuweisung erwarten würde.
Antworten