Nichtexistente Attribute von Klasse ansprechen (für Wrapper)

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.
Benutzeravatar
snafu
User
Beiträge: 6861
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Jetzt stehe ich vor einer wirklich haarigen Angelegenheit: Mein Wrapper soll auch Funktionsaufrufe erkennen können. `js.foo.bar().baz` bekomme ich hin und theoretisch sollte er auch Argumente in die Klammer packen können. Das Problem an `js.foo.bar(spam, eggs).baz` ist jedoch, dass Python sich vorher einschaltet, da es `spam` und `eggs` nicht kennt. Logisch, schließlich sollen diese nur im Kontext des JS-Codes identifiziert werden. Gibt es irgendeine Möglichkeit, Namen sozusagen an dem Python-Interpreter vorbeizuschleusen, um sie - obwohl für diesen nicht existent - an die Klasse übergeben zu können? Mir fällt gerade absolut nichts ein. :(

http://paste.pocoo.org/show/118104/

Wenn's nicht anders geht (was ich befürchte), muss man Argumente eben als String an den Wrapper übergeben, das ist mir klar.

EDIT: Glaube jetzt nach längerem Nachdenken kaum, dass dies möglich ist und finde mich mit der String-Lösung ab. Das Annehmen von `*args` muss natürlich nicht in `__init__()` sondern in `__call__()` geschehen.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Bedenke bitte, das Du im Pythonkontext arbeitest und den JS-Kontext bedienen willst. Diese zu Einem zusammenzufassen (bzw. impliziter Kontextwechsel), wäre denkbar schlecht, der Ursprung der Variable oder die Sichtbarkeit werden schnell unklar und Kollisionen sind kaum zu vermeiden. Eine JS-Variable, die Python nicht sieht, sollte im Pythonkontext auch als nicht definiert behandelt werden und umgekehrt.

Eine mögliche Lösung des Problems wäre, den Kontextwechsel auch syntaktisch, also explizit anzuzeigen (steigert auch die Lesbarkeit):

Code: Alles auswählen

js = JSContext()
js.bar # == var bar;
'bar' wäre jetzt wiederum über ein __getattribute__()-Hook zu JS mapfähig.

In JS gibts jetzt aber nicht nur Objekte, Properties und Funktionen. Wie willst Du denn die komplexeren Sprachkonstrukte abbilden?
Benutzeravatar
HerrHagen
User
Beiträge: 430
Registriert: Freitag 6. Juni 2008, 19:07

bords0 hat geschrieben:Oder meinst du, dass das default-Argument nur einmal berechnet wird, und dann stets mit einer Kopie gearbeitet wird? Das hat wieder andere Probleme.
Genau das meine ich. Was wären denn die Probleme?
Leonidas hat geschrieben:Also nachdem die Funktionsaufrufe in Python eh schon so aufwendig sind, magst du sie noch langsamer machen? Ich finde auch, dass es sich nicht lohnt, schließlich hat man so fälle auch nicht so oft. Mutable Objekte sind sowieso des Teufels :twisted:
Seit wann wird die Syntax von Python nach Geschwindigkeit ausgerichtet? Ich dachte immer Klarheit geht vor Performanz...
Außerdem glaub ich nicht das die Performanceeinbuse so groß sein dürfte. Schließlich kann das bisherige Verhalten für die (eingebauten) unveränderlichen Objekte (bei denen kann man sich sicher sein) unverändert bleiben - was ja auch der Großteil der Fälle wäre, wie du selber sagst.

MFG HerrHagen
Benutzeravatar
snafu
User
Beiträge: 6861
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich will eigentlich keinen komplett ausgestatteten Wrapper machen. Er soll eher so zugeschnitten sein, dass mein angefangenes `pybrowse`-Projekt damit umgehen kann und es trotzdem abstrakt genug bleibt, um auch von anderen Programmen genutzt zu werden.

Zusätzlich bemerke ich jetzt schon den großen Lerneffekt, den mir das bringt, um Python etwas besser zu verstehen. So bin ich jetzt z.B. von der `__repr()__` Variante weg, als mir auffiel, dass der Ausgabewert zwar als String im Interpreter angezeigt wird, aber in Wirklichkeit natürlich immer noch das Objekt ist. Solche Sachen halt.

Der aktuelle Stand unterstützt jetzt auch Längenabfragen, Slicing und Listen. :) Allzu viel sollte man ohnehin nicht erwarten. Der Über-Python-Guru bin ich wohl nicht gerade... ;)
BlackJack

@HerrHagen: Man kann nicht alle Objekte kopieren. Zum Beispiel Dateiobjekte, also so etwas wie ``def spam(a, b, out_file=sys.stdout):`` ginge dann nicht.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@snafu:
Deinen Wrapper-Code habe ich mir noch nicht näher zu Gemüte geführt, aber Dein Umgang mit der QApplication ist ziemlich fragwürdig. Du verbindest das 'loadFinished'-Signal mit mit dem Schließen der QApplication, während Du mit dem Wrapper weiterhin auf Qt-Datentypen zugreifst :shock: Genau genommen existiert die Qt-Anwendung innerhalb der test-Funktion nur während des br.open()-Aufrufs, der Event-Loop hält die weitere test()-Ausführung bis zum 'loadFinished' an, dann wird dieser mit quit() verlassen. Da PyQt von C++ abstammt, ist nicht einmal sicher, ob zu dieser Zeit nicht schon irgenwelche Destruktoren das Datenmodell "auflösen" und Du anschliessend illegale Zugriffe tätigst.

Alternativ-Vorschlag:
Browser() mit Funktionscallback initialisieren, abarbeiten lassen, dann Qt gezielt "runterfahren".
Benutzeravatar
snafu
User
Beiträge: 6861
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Naja, das Problem an Callbacks sehe ich darin, das AFAIK keine Returnwerte zurückgegeben werden können. Ich kann mir zwar verschiedene Dinge anzeigen lassen, während `app` läuft, aber die Dinge eben nur abspeichern, wenn ich sie in Attribute der Klasse schreibe. Mir ist klar, dass die Sitzung nach jeden `quit()` beendet ist.

Allerdings hat man ja dann trotzdem den JS-Code, weiß was man öffnen soll und lässt ihn mit JS eben Sachen ausfüllen, abschicken usw. Wobei über "usw" wohl noch zu sprechen wäre. Z.B. weiß ich nicht wie man Links anklickt. Wohlgemerkt: keine neuen URL's öffnet, sondern einen Klick simuliert (das kann ja durchaus unterschiedlich von den Seiten behandelt werden).

Außerdem glaube ich, dass man Frames in ihrem aktuellen Zustand auch wieder einspeisen kann.

Hier mal die beiden Dateien zusammen:

pybrowse.py (`submit()` ist noch ungetestet, sollte aber theoretisch so laufen)

jswrapper.py (an `__setitem___()` tüftel ich noch bzw habe gerade eine Denkblockade)

Also nochmal meine Intention, falls das nicht ganz rübergekommen ist: Ich will, ähnlich wie bei `mechanize` eine Seite öffnen, dann Zugriff auf den aktuellen Code haben, ein Feld ausfüllen, evtl. zwischendurch im Fenster anzeigen lassen ob das richtige ausgefüllt wurde, abschicken, das Ergebnis angucken usw. Dafür ist es m.E. nötig, die Sitzung zu unterbrechen - sprich: aus dem Eventloop zu kommen - dann seine Klamotten zu machen, um danach einigermaßen elegant zur bisherigen Stelle zu kommen, wo man sich dann alles angucken kann. Wenn ein Browser-Skript fertig ist, bietet es sich natürlich an, dieses in einem Stück ausführen zu lassen, also ohne ständige Unterbrechungen. Diese sind halt eher zum Testen im Interpreter gedacht.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

HerrHagen hat geschrieben:Seit wann wird die Syntax von Python nach Geschwindigkeit ausgerichtet? Ich dachte immer Klarheit geht vor Performanz...
Ähm, Generatoren?
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@snafu:
Zu der quit-Sache - laut Qt dürfte das Stoppen des MainEventLoops und anschliessende Zugriff auf Qt-Typen ohne Seiteneffekte funktionieren. Prinzipiell sollte sogar das erneute Anstossen des Loops mit QApplication::exec() gehen. Beides wird aber nicht empfohlen weil ungetestet.
bords0
User
Beiträge: 234
Registriert: Mittwoch 4. Juli 2007, 20:40

@HerrHagen: BlackJack hat ja schon ein Problem genannt: Nicht jedes Objekt ist kopierbar. Außerdem: Flache oder tiefe Kopie? Wahrscheinlich tiefe - sonst kann es immer noch Änderungen geben.

Außerdem muss bereits bei der Definition eine tiefe Kopie durchgeführt werden (von der dann später weitere tiefe Kopien angefertigt werden), da sich sonst noch nachträgliche Änderungen ergeben können:

Code: Alles auswählen

a = []
def spam(x=[a]): print x
spam()
a.append(1)
spam()
Dabei soll bei dir ja beide Male [[]] ausgegeben werden, wenn ich dich richtig verstanden habe.

Es gibt m.W. auch Anhänger der Variante, dass die defaults jedesmal neu ausgewertet werden, also dass z.B.

Code: Alles auswählen

def foo(x=random.random()): print x
jedesmal eine andere Zahl ausgibt.

"Das def-statement wird ausgeführt, fertig" halte ich insgesamt für eine gute Lösung. Es kann am Anfang verwirren, keine Frage. Dadurch lernt man dann aber auch schnell, was wirklich passiert: eben "das def-statement wird ausgeführt, fertig".

(Bin ich zu müde für vernünftige Antworten?)
Benutzeravatar
HerrHagen
User
Beiträge: 430
Registriert: Freitag 6. Juni 2008, 19:07

@BlackJack&Bords0: hmm, das mit den nicht kopierbaren objekten bzw. der Problematik flache/tiefe Kopie ist ein echtes Argument. Da muss ich erstmal drüber nach denken.
Danke erstmal für die Diskussion; ich versteh jetzt ein Stück besser warum die Dinge so sind wie sie sind.

MFG HerrHagen
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Dieser Weg ist außerdem konsequent. Jedes Statement in Python wird direkt ausgeführt, warum sollte das bei def anders sein? Würde man es anders machen, müsste man jede Menge Magie zu def hinzufügen.

Außerdem bietet der Python Weg einfach mehr Möglichkeiten. Man kann immer nachträglich im Funktionskörper ein Objekt aus den Standardparametern kopieren (und kann sich dann um die Problematik des flachen und tiefen Kopierens selber kümmern). Genauso kann man auch einfach (lambda) Funktionen übergeben, wenn bestimmte Parameter unbedingt immer wieder neu ausgewertet werden müssen. Das ist auch imho der klarere und eigentlich gesuchte Weg: Statt zb. random.random() immer neu auswerten zu lassen, fordert man einfach eine "Zufallsfunktion" an und setzt sie standardmässig auf random.random und fertig. Was soll eigentlich mit Funktionen selbst passieren, wenn sie kopiert werden sollen? Spätestens hier wird es dann sehr unperformant.

Das Verhalten ist anfangs ungewohnt, fügt sich aber besser in Python ein als jede andere Möglichkeit, bietet die größtmögliche Freiheit, es wird keine Ausnahmen für Funktionen was Zuweisungen angeht eingeführt und zusätzlich ist es noch performant.

EDIT: Gut random ist dafür nicht das beste Beispiel. Aber da könnte man ja auch einfach None zum Standard erklären und dann mit random.random() eine Zahl generieren, wurde keine vom Aufrufer übergeben.
Antworten