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.
bords0
User
Beiträge: 234
Registriert: Mittwoch 4. Juli 2007, 20:40

HerrHagen hat geschrieben:
bords0 hat geschrieben:Deshalb halte ich es für etwas klarer, das Problem nicht auf die mutablen Default-Argumente zu schieben, sondern auf das Mutieren selbst.
Das Problem mit der Geschichte ist, dass der Umstand, dass ein default-Argument nur einmalig erzeugt wird, vielen nicht bekannt,
Richtig.
überdies auch sehr unintuitiv,
Eigentlich nur, bis man verstanden hat, dass das "def"-Statement tatsächlich ausgeführt wird - und zwar zur Definition der Funktion, nicht beim Aufruf derselben.
und IMO auch ziemlich unsinnig ist.
Da sind wir überhaupt nicht einer Meinung... weil es IMO keine sinnvollere Alternative gibt.
Um solche Probleme wie oben gesehen zu vermieden, sollte man auf solche Sachen von vornherein verzichten. Wenn das Objekt also sowieso nicht verändert werden sollte, dann sollte dies auch durch die Datenstruktur entsprechend abgebildet werden (also unveränderliche Liste = tuple).
Das ist m.E. zwar i.W. richtig, aber es gibt Ausnahmen.

Code: Alles auswählen

>>> def shuffled_range(n, extra=[]):
	import random
	r = range(n) + extra
	random.shuffle(r)
	return r

>>> shuffled_range(5)
[2, 0, 4, 3, 1]
>>> shuffled_range(10, [-1, "Zehn"])
['Zehn', 9, 6, -1, 5, 0, 8, 3, 7, 1, 4, 2]
>>> shuffled_range(5, ())

Traceback (most recent call last):
  File "<pyshell#9>", line 1, in <module>
    shuffled_range(5, ())
  File "<pyshell#5>", line 3, in shuffled_range
    r = range(n) + extra
TypeError: can only concatenate list (not "tuple") to list
Oder auch

Code: Alles auswählen

>>> class Spam(object):
	def __init__(self, attrs={}):
		self.__dict__.update(attrs)

		
>>> s = Spam()
>>> s.__dict__
{}
>>> t = Spam({"foo": "bar", "baz": "blubb"})
>>> t.__dict__
{'foo': 'bar', 'baz': 'blubb'}
>>> t.baz
'blubb'
Letzteres würde zwar auch mit einem leeren tuple funktionieren, aber so finde ich es viel klarer. (Naja, mit einem nicht-leeren tuple wäre das Beispiel wohl besser gewesen.)
Benutzeravatar
HerrHagen
User
Beiträge: 430
Registriert: Freitag 6. Juni 2008, 19:07

Eigentlich nur, bis man verstanden hat, dass das "def"-Statement tatsächlich ausgeführt wird - und zwar zur Definition der Funktion, nicht beim Aufruf derselben.
Das widerspricht ein wenig meinem Verständnis von Intuition. Heißt Intuition nicht etwas (intuitiv) richtig zu machen, ohne alle zugrunde legenden Mechanismen zu kennen?
Da sind wir überhaupt nicht einer Meinung... weil es IMO keine sinnvollere Alternative gibt.
Die Alternative ist doch offensichtlich. Die Argumente verhalten sich so wie man es aus der Sntax heraus erwarten würde. Also ein Default ist auch wirklich ein Default und bleibt auch immer das gleiche.
Alles was IMO dagegen spricht ist ein Performance-Problem. Allerdings kann ich das nicht so recht glauben. Schließlich kann das Verhalten (Objekt wird nur einmal erzeugt) für immutable Datentypen (was bestimmt 95% der Fälle ausmacht) unverändert bleiben.
Mit fällt kein Beispiel ein in dem das 'wir erzeugen die default werte nur einmal'-Verhalten sinnvoller wäre.
Das ist m.E. zwar i.W. richtig, aber es gibt Ausnahmen.
Damit das man durchaus auch veränderliche Objekte als Argumente verwenden kann (wenn man sie nicht verändert), hast du natürlich Recht. In deinen Bsp. spricht auch nichts dagegen.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

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:
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
BlackJack

@HerrHagen: Woran erkennt man das Objekte "(im)mutable" sind? Bis auf ein paar Standardtypen muss man eigentlich immer davon ausgehen das ein Objekt "mutable" ist. Es kann sein, dass es das Wert-Muster implementiert, aber man kann es trotzdem verändern, wenn man denn will.

Ein Beispiel für eine sinnvolle Verwendung dieses Features sind übrigens Funktionen mit "Gedächtnis", also zum Beispiel ein Dictionary als Cache.

Edit: Und was die Intuition angeht, gibt's im anderen Fall sicher Leute die sich beschweren, dass bei ``def f(a, b=g(42)):`` die `g()`-Funktion bei jedem Aufruf von `f()` ausgeführt wird.
bords0
User
Beiträge: 234
Registriert: Mittwoch 4. Juli 2007, 20:40

HerrHagen hat geschrieben:Das widerspricht ein wenig meinem Verständnis von Intuition. Heißt Intuition nicht etwas (intuitiv) richtig zu machen, ohne alle zugrunde legenden Mechanismen zu kennen?
Intuitiv bedeutet m.E., dass man ohne weitere Schlussfolgerungen automatisch zum richtigen Ergebnis kommt - ohne das Ergebnis zu kennen. Den Kontext darf man schon kennen.
HerrHagen hat geschrieben:Die Alternative ist doch offensichtlich. Die Argumente verhalten sich so wie man es aus der Sntax heraus erwarten würde. Also ein Default ist auch wirklich ein Default und bleibt auch immer das gleiche.
Wenn ich dich richtig verstehe, meinst du, dass die default-Argumente bei jedem Funktionsaufruf neu ausgewertet werden. (Oder nur, falls nötig? So offensichtlich ist mir das nicht!) Dann bekommt man aber i.A. jedesmal ein anderes Objekt, welches du als "immer das gleiche" bezeichnest. Aber z.B. bei

Code: Alles auswählen

def f(x=a + b): return x**2
finde ich es nicht mehr intuitiv, dass der default-Wert immer erst zur Laufzeit bestimmt wird, und damit verschiedene Werte und Typen annehmen kann, oder auch gar nicht erst bestimmt werden kann. Und das damit zu begründen, dass das Default immer das gleiche bleiben soll, finde ich arg weit hergeholt.

Oder meinst du, dass das default-Argument nur einmal berechnet wird, und dann stets mit einer Kopie gearbeitet wird? Das hat wieder andere Probleme.
Benutzeravatar
snafu
User
Beiträge: 6738
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: 6738
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: 6738
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