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: 6862
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hi,

ich will mir einen Wrapper schreiben, um in Qt leichter JavaScript-Code auf Webseiten anwenden zu können. Qt bietet einem als Schnittstelle die Methode evaluateJavaScript() an, wo der Code als QString übergeben wird.

An sich funktioniert das auch. Will ich zum Beispiel den 5. Link auf einer Seite haben, dann sage ich

Code: Alles auswählen

evaluateJavaScript(QString('document.links[4].href')).toString()
Was ich jetzt möchte, ist dass mir etwas wie `wrapper.document.links[4].href` das selbe Ergebnis liefert. Die Klasse dürfte also bei diesem Zugriff zum einen keinen `AttributeError` werfen und müsste zum anderen sozusagen die Hierarchie der angeforderten Attribute erkennen, diese umwandeln und das Ergebnis zurückliefern.

Also für die erste Ebene ist es kein Problem:

Code: Alles auswählen

In [3]: class Bla(object):
   ...:     def __getattr__(self, attr):
   ...:         return attr
   ...:     
   ...:     

In [4]: bla = Bla()

In [5]: bla.blupp
Out[5]: 'blupp'
Allerdings wirft mir `bla.blupp.mauz` einen Fehler:

Code: Alles auswählen

AttributeError: 'str' object has no attribute 'mauz'
Und ich stehe gerade echt auf dem Schlauch, wie ich das lösen kann. Wäre nett, wenn ich mich jemand in die richtige Richtung stubsen könnte. ;P
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

In "__getattr__" gibst du nur den Namen des angeforderten Attribut zurück, also einen einfach String. Und der hat natürlich kein Attribut "mauz". Vielleicht möchtest du eine Instanz von "Bla" zurückgeben und nicht "attr"?

Benutze für Dummy-Namen doch "spam" und "eggs" oder "foo", "bar", "baz", sonst kommt man vollkommen durcheinander ^^
Das Leben ist wie ein Tennisball.
Benutzeravatar
Dill
User
Beiträge: 470
Registriert: Mittwoch 10. Januar 2007, 14:52
Wohnort: Köln

wie wärs damit?

Code: Alles auswählen

class Foo(object):
    def __init__(self, name):
        self.name = name
    def __getattr__(self, name):
        return Foo(name)
http://www.kinderpornos.info
Benutzeravatar
snafu
User
Beiträge: 6862
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Danke, das war schon mal eine Hilfe. Hatte vorher versucht, `self` zurückzugeben, aber das geht anscheinend nicht. So ganz blicke ich aber noch nicht durch. Ich habe jetzt:

Code: Alles auswählen

In [126]: class Foo(object):
   .....:     def __init__(self, parent=[]):
   .....:         self.parent = parent
   .....:     def __getattr__(self, attr):
   .....:         self.parent.append(attr)
   .....:         return Foo(self.parent)
   .....:     def __str__(self):
   .....:         return '.'.join(self.parent)
   .....:     
   .....:     

In [127]: foo = Foo()

In [128]: print foo


In [129]: print foo.bar
bar

In [130]: print foo.bar.baz
bar.bar.baz
Wo ist hier der Fehler, den ich nicht finde? Das ist für mich nämlich momentan ganz schön viel um die Ecke denken, wenn ich ehrlich bin.
Benutzeravatar
Dill
User
Beiträge: 470
Registriert: Mittwoch 10. Januar 2007, 14:52
Wohnort: Köln

in zeile 6?
http://www.kinderpornos.info
Benutzeravatar
HerrHagen
User
Beiträge: 430
Registriert: Freitag 6. Juni 2008, 19:07

Du solltest niemals ein veränderliches Objekt (wie eine Liste in Zeile 2) als default-Argument einer Funktion nehmen. Das Objekt wird nur einmal erzeugt und dann wiederverwendet.

Code: Alles auswählen

>>> class Foo(object):
	def __init__(self, parent=[]):
		self.parent = parent
	def __getattr__(self, attr):
		self.parent.append(attr)
		return Foo(self.parent)
	def __str__(self):
		return '.'.join(self.parent)

>>> a = Foo()
>>> print a.B
B
>>> print a.B
B.B
>>> print a.B
B.B.B
>>> print a.B
B.B.B.B
MFG HerrHagen
Benutzeravatar
HerrHagen
User
Beiträge: 430
Registriert: Freitag 6. Juni 2008, 19:07

Achso, ... hier mal wie man es lösen könnte:

Code: Alles auswählen

>>> class Foo(object):
	def __init__(self, _parent=()):
		self._parent = _parent
	def __getattr__(self, attr):
		return type(self)(self._parent + (attr,))
	def __str__(self):
		return '.'.join(self._parent)

	
>>> a = Foo()
>>> print a.B
B
>>> print a.B
B
>>> print a.B.C
B.C
>>> print a.B.C.D
B.C.D
Benutzeravatar
snafu
User
Beiträge: 6862
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Dankeschön. :) Das mit den Listen war mir neu.
bords0
User
Beiträge: 234
Registriert: Mittwoch 4. Juli 2007, 20:40

HerrHagen hat geschrieben:Achso, ... hier mal wie man es lösen könnte:

Code: Alles auswählen

>>> class Foo(object):
	def __init__(self, _parent=()):
		self._parent = _parent
	def __getattr__(self, attr):
		return type(self)(self._parent + (attr,))
	def __str__(self):
		return '.'.join(self._parent)

	
>>> a = Foo()
>>> print a.B
B
>>> print a.B
B
>>> print a.B.C
B.C
>>> print a.B.C.D
B.C.D
Lustig ist dabei, dass, wenn man den Algorithmus auf "Nicht-Mutation" umgestellt hat, auch mutable Default-Argumente verwenden kann.

Code: Alles auswählen

>>> class Foo(object):
    def __init__(self, _parent=[]):
        self._parent = _parent
    def __getattr__(self, attr):
        return type(self)(self._parent + [attr])
    def __str__(self):
        return '.'.join(self._parent)

>>> a = Foo()
>>> print a.B
B
>>> print a.B
B
Deshalb halte ich es für etwas klarer, das Problem nicht auf die mutablen Default-Argumente zu schieben, sondern auf das Mutieren selbst.
Benutzeravatar
HerrHagen
User
Beiträge: 430
Registriert: Freitag 6. Juni 2008, 19:07

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, überdies auch sehr unintuitiv und IMO auch ziemlich unsinnig ist. 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).

MFG HerrHagen
Benutzeravatar
Dill
User
Beiträge: 470
Registriert: Mittwoch 10. Januar 2007, 14:52
Wohnort: Köln

also das ist ja echt unheimlich. was macht das fürn sinn?

Code: Alles auswählen

In [2]: class M:
   ...:     def __init__(self, m=[]):
   ...:             self.m = m
   ...:     def to_m(self, d):
   ...:             self.m.append(d)
   ...:

In [3]: m=M()

In [4]: m.to_m(1)

In [5]: m.m
Out[5]: [1]

In [6]: del m

In [7]: m
---------------------------------------------------------------------------
NameError: name 'm' is not defined

In [8]: m2=M()

In [9]: m2.m
Out[9]: [1]
http://www.kinderpornos.info
BlackJack

@Dill: Default-Werte werden halt einmal, wenn das ``def`` ausgeführt wird, ausgewertet. Die Alternative wäre ja bei *jedem* Aufruf der Funktion. Was deutlich mehr Overhead bedeuten würde, und das für Ausdrücke, die in der Regel immer das gleiche Ergebnis haben. Also völlig unnötiger Mehraufwand in 99,9% der Fälle.

Edit: Nochmal zur Verdeutlichung wo diese Liste in Deinem Beispiel steckt:

Code: Alles auswählen

In [223]: m = M()

In [224]: m.to_m(42)

In [225]: M.__init__.im_func.func_defaults
Out[225]: ([42],)
Die ist also über die Klasse erreichbar, verschwindet deshalb natürlich nicht, wenn man Exemplare von `M` löscht.
Zuletzt geändert von BlackJack am Montag 18. Mai 2009, 09:46, insgesamt 1-mal geändert.
Benutzeravatar
Dill
User
Beiträge: 470
Registriert: Mittwoch 10. Januar 2007, 14:52
Wohnort: Köln

ich habe hier aber eine neue instanz erstellt und die enthält noch die daten aus der ersten instanz ('1')
http://www.kinderpornos.info
Benutzeravatar
helduel
User
Beiträge: 300
Registriert: Montag 23. Juli 2007, 14:05
Wohnort: Laupheim

Dill hat geschrieben:ich habe hier aber eine neue instanz erstellt und die enthält noch die daten aus der ersten instanz ('1')
Nein, du hast lediglich die einmal beim Ausführen von "def __init__(...)" erstellte Liste an deine Instanz gebunden.

Gruß,
Manuel
Benutzeravatar
snafu
User
Beiträge: 6862
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Sollte dann erstmal fertig sein. Ich danke für die Hilfe. :)

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

EDIT: Achso, Items setzen mach ich nachher.
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.
Antworten