Seite 1 von 2

Nichtexistente Attribute von Klasse ansprechen (für Wrapper)

Verfasst: Sonntag 17. Mai 2009, 18:34
von snafu
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

Verfasst: Sonntag 17. Mai 2009, 19:07
von EyDu
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 ^^

Verfasst: Sonntag 17. Mai 2009, 19:12
von Dill
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)

Verfasst: Sonntag 17. Mai 2009, 19:47
von snafu
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.

Verfasst: Sonntag 17. Mai 2009, 19:57
von Dill
in zeile 6?

Verfasst: Sonntag 17. Mai 2009, 20:10
von HerrHagen
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

Verfasst: Sonntag 17. Mai 2009, 20:30
von HerrHagen
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

Verfasst: Sonntag 17. Mai 2009, 20:37
von snafu
Dankeschön. :) Das mit den Listen war mir neu.

Verfasst: Sonntag 17. Mai 2009, 22:02
von bords0
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.

Verfasst: Montag 18. Mai 2009, 08:47
von HerrHagen
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

Verfasst: Montag 18. Mai 2009, 09:17
von Dill
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]

Verfasst: Montag 18. Mai 2009, 09:41
von 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.

Verfasst: Montag 18. Mai 2009, 09:44
von Dill
ich habe hier aber eine neue instanz erstellt und die enthält noch die daten aus der ersten instanz ('1')

Verfasst: Montag 18. Mai 2009, 10:34
von helduel
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

Verfasst: Montag 18. Mai 2009, 11:30
von snafu
Sollte dann erstmal fertig sein. Ich danke für die Hilfe. :)

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

EDIT: Achso, Items setzen mach ich nachher.

Verfasst: Montag 18. Mai 2009, 20:28
von bords0
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.)

Verfasst: Dienstag 19. Mai 2009, 11:29
von HerrHagen
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.

Verfasst: Dienstag 19. Mai 2009, 17:08
von Leonidas
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:

Verfasst: Dienstag 19. Mai 2009, 20:07
von 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.

Verfasst: Dienstag 19. Mai 2009, 20:19
von bords0
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.