Special methods lookup prozess

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
dasheck
User
Beiträge: 5
Registriert: Mittwoch 10. September 2014, 11:30

Hi,

ich hab eine eher theoretische Frage und hoffe, dass ich dir hier mit jemanden (Plural ;)) diskutieren kann/darf. Es geht um den lookup Prozess von special methods (__len__, __init__, ...). Im Gegensatz zum normalen lookup Prozess wird ja direkt in der Klasse angesetzt und nicht bereits in der Instanz. Daraus ergeben sich zwei Fragen:

1) Ist dies der einzige Unterschied zwischen den beiden Prozessen (normale methods und special methods).
2) Warum sietzt der lookup für special methods erst auf Klassenebene an?

Zu 2) wollte ich vorab schon meine Gedanken mit euch teilen. Gerade solche Methoden wie __len__ und __add__ beschreiben ja ein Klassenspezifisches Verhalten (wie addiere ich zwei Instanzen von Integer/String/...) und sind nur abhängig von den attributen der Instanz, die man mit self ja abgreifen kann. Das trifft aber meiner Meinung nach ja häufig auch auf normale Methoden zu und diese werden bereits auf Instanz ebene gesucht.

Hängt es damit zusammen, dass special methods die Protokolle implizieren und daher dieser spezielle Lookup durchgeführt werden muss?

Danke für eine hoffentlich fruchtbare Diskussion.

Grüße
dasheck
BlackJack

@dasheck: Ad 1) Ich vermute sehr stark das bei den speziellen Methoden auch keine Deskriptoren funktionieren.

Ad 2) Geschwindigkeit vermute ich. Man spart ja bei jedem Zugriff eine ”Ebene” Tests und Indirektionen. Und da die speziellen Methoden in der CPython-Implementierung AFAIK alle als Felder direkt in der `PyObject`-Struktur stehen, können der Interpreter und Erweiterungen die in C geschrieben sind, darauf noch direkter zugreifen weil sie nicht einmal bei der Klasse über eine `PyDict`-Struktur und entsprechende Funktionsaufrufe gehen müssen.
dasheck
User
Beiträge: 5
Registriert: Mittwoch 10. September 2014, 11:30

Danke für die rasche Antwort. Das Argument mit der Geschwindigkeit ist sicherlich legitim erklärt aber nicht den Unterschied, warum special methods einen anderen Lookup Prozess haben als normale Methoden. Warum sucht man normale Methoden auch nicht erst ab Klassenebene. Desweiteren vermute ich noch einen anderen konzeptionellen Grund hinter der Maßnahme. In Vorbereitung auf eine Prüfung wurde uns diese Frage gestellt und solche Dinge wie Geschwindigkeit oder direktere Anbindung für APIs waren nie Bestandteil unserer Vorlesung/Argumentation. Das heißt es muss darüber hinaus noch etwas geben. Ich werde dennoch sicherlich Geschwindigkeit als Begründung mitliefern. Also vielen Dank dafür bereits :)

Meine erste Frage zielte auch eher darauf ab ob sich die beiden Prozess sonst wie noch unterschieden (Andere MRO-Reihenfolge, verschiedene Endpunkte, was passiert bei Methode nicht gefunden, etc). Das mit den nicht funktionierenden Deskriptoren würde ich als Implikation des veränderten Lookup prozesses sehen.
BlackJack

@dasheck: Da diese Änderung mit „new style classes” kam, könntest Du Dir vielleicht überlegen was es dort für Implikationen hätte wenn man die speziellen Methoden erst auf Exemplaren und dann erst in der Klasse suchen würde.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

dasheck hat geschrieben:Warum sucht man normale Methoden auch nicht erst ab Klassenebene.
Was sind den Methoden? Woher weiß ich dass ich eine Methode suche? Gibt es Methoden überhaupt? Versuch die Fragen mal zu beantworten und dir wird klar wieso man es nicht tut. Mal abgesehen von den offensichtlichen Problemen im Bezug auf Rückwärtskompatibilität was hier aber noch nichtmal wirklich relevant ist weil es schon vorher scheitern muss.
Desweiteren vermute ich noch einen anderen konzeptionellen Grund hinter der Maßnahme. In Vorbereitung auf eine Prüfung wurde uns diese Frage gestellt und solche Dinge wie Geschwindigkeit oder direktere Anbindung für APIs waren nie Bestandteil unserer Vorlesung/Argumentation. Das heißt es muss darüber hinaus noch etwas geben. Ich werde dennoch sicherlich Geschwindigkeit als Begründung mitliefern. Also vielen Dank dafür bereits :)
Was der ursprüngliche Grund war weiß wahrscheinlich keiner mehr so genau. Geschwindigkeit mag vielleicht ein Aspekt davon gewesen sein, tatsächlich ist es aber nicht schneller im Gegenteil es ist sogar messbar langsamer. Armin Ronacher hat sich darüber erst vor kurzem in einem Artikel beschwert.
dasheck
User
Beiträge: 5
Registriert: Mittwoch 10. September 2014, 11:30

Methoden sind Objekte, genauer gesagt bound method objects, die an eine Instanz gebunden sind und zudem callable sind. Daher sucht man diese auch nicht erst ab Klassenebene, weil diese nicht an eine Klasse gebunden werden können, sondern immer nur an ihre Instanz. Das macht vorerst den Unterschied klar. Mir ist aber noch nicht ganz ersichtlich warum man hingegen special methods nicht auch schon ab Instanzebene suchen sollte. Mir sind dazu noch zwei Gedanken gekommen:

1) Der Unterschied zwischen special methods und normalen methoden ist ja der, dass man mit special methods ein Python Protokoll 'deklarieren' kann (e.g. Iterator protocol).

2) Der Vorteil, den man hat, wenn man schon ab Instanzebene sucht ist, dass man durch manipulieren von __dict__ in der Instanz die Methode überschreiben möchte. Ist das der Grund? Will man erreichen, dass eine special method für jede Instanz einer Klasse das gleiche Verhalten aufweist?

Grüße
heck

EDIT: Um die Verwirrung zu erhöhen

Code: Alles auswählen

class A():
	def __len__(self):
		return 0
		
a = A()
a.__len__() # -> 0

def len():
	return 1
	
a.__dict__['__len__'] = len
a.__len__() # -> 1
Da ich __len__ nur auf Instanzeben gesetzt habe, erwarte ich eigentlich, dass a.__len__() imemrnoch 0 zurückliefert, da der Lookup prozess erst auf Klassenebene einsetzt?
BlackJack

@dasheck: Nach Lektüre von Armin Ronacher's Blogeintrag den DasIch verlinkt hat, gehe ich jetzt davon aus das es keinen objektiven Grund gibt, sondern dass das einfach ein Überbleibsel aus grauer Vergangenheit ist weil das bei in C implementierten Typen schon immer so gemacht wurde. Also als es noch einen Unterschied zwischen „types” und (old style) „classes” gab. Und im Zuge der Harmonisierung mittels „new style classes” als Ergebnis, wird das nun in jedem Objekt so gehandhabt, welches aus einer „new style”-Klasse entstanden ist.

Ansonsten können auch noch komische Situationen entstehen weil Klassen ja auch Objekte sind. Wenn die speziellen Methoden immer erst auf dem Exemplar gesucht werden würden, dann haben die Klassen ja (ungebundene) Methoden die eigentlich für Exemplare gedacht sind. Beispiel `__str__()`:

Code: Alles auswählen

class A(object):
    def __str__(self):
        return 'A'

a = A()
print A
print a
Hier wird beim ersten ``print`` in der Klasse von `A` nach der Methode gesucht, also ``type.__str__(A)`` aufgerufen. Beim zweiten ``print`` wird in der Klasse von `a`, also in `A` nach der Methode gesucht und ``A.__str__(a)`` aufgerufen. Und nun überleg mal welche Methoden mit welchen Argumenten aufgerufen würden, wenn `__str__` zuerst auf dem Objekt gesucht würde welches man in eine Zeichenkette umwandeln möchte.
dasheck
User
Beiträge: 5
Registriert: Mittwoch 10. September 2014, 11:30

Ich bin mir nicht ganz sicher was du meinst, daher gebe ich erstmal wieder wie ich es verstanden habe. Ich lege dein letztes snippet zu Grunde und gehe dann von aus, dass ich zuerst in der Instanz nach dem Methodenobjekt '__str__' suche und dieses nicht darin vorhanden ist.

Würde __getattr__ einbezogen werden und vermutlich dann in die Klassenebene hochgehen. Bei der Recherche ist mir allerdings dieser Link in die Hände geflogen indem klar gestellt wird, dass beim lookup für special methods __getattr__ und __getattribute__ gar nicht involviert werden.

Würden wir nun von aus gehen, dass '__str__' auf Instanzebene vorhanden ist, dann würde diese aufgerufen werden mit nichts als Argument also 'a.__str__()'?
dasheck
User
Beiträge: 5
Registriert: Mittwoch 10. September 2014, 11:30

Sorry für den Doppelpost, aber ich wollte sichergehen, dass das auch wahrgenommen wird und nicht im EDIT meines vorhergehenden untergeht.

Nach der Lektüre von der offiziellen Dokumentation, komme ich zu dem Schluss, dass es tatsächlich mit Geschwindigkeit aber noch viel mehr mit Konsistenz zu tun hat.
[...] the special method must be set on the class object itself in order to be consistently invoked by the interpreter
Man kann special methods explizit aufrufen a.__str__() bzw. type(a).__str__(a) und implizit mittels str(a). Im impliziten Aufruf wird aber der lookup Prozess von Python ausgehebelt (um Geschwindigkeit zu optimieren). Mit anderen Worten __getattribute__ aus der Klasse und Metaklasse wird nie aufgerufen. Ohne weiter zu beschrieben wie und was wurde gesagt, dass das der Grund ist warum special methods innerhalb des class scope deklariert werden müssen. Das nächste Beispiel sollte darüber nochmal Aufschluss geben:

Code: Alles auswählen

class C:
	pass
	

c = C()	
c.__len__ = lambda: 5

c.__len__() # 5
len(c) # 1
Durch den impliziten Aufruf wird der lookup umgeschmissen und die auf Klassenebene (object) geklarierte Methode __len__ aufgerufen und nicht die in der Instanz gesetze. Daher müssen (um stets zu funktionieren) special methods immer auf Klassenebene angegeben werden.
BlackJack

@dasheck: Deine Erklärung ist jetzt aber schon sei ein bisschen wie „Das ist so, weil es so ist”. ;-)

Das Problem ist nicht was passiert wenn `__str__` direkt auf dem Exemplar `a` existiert sondern das `A` auch ein Exemplar ist (von der Klasse `type`) und *dort* findet man *tatsächlich* direkt auf diesem Exemplar ein `__str__`, welches aber gar nicht zur Darstellung von `A` gedacht ist sondern zur Darstellung von Exemplaren von `A`. Für die Darstellung von `A` ist die `__str__()`-Implementierung von der Klasse von `A`, also `type.__str__`, zuständig. Nur würde man zu dieser Methode nicht kommen wenn man direkt auf dem Objekt mit dem suchen startet, wie man das bei anderen, nicht-speziellen Methoden macht.
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

Jetzt habe ich angefangen mir Gedanken über special methods zu machen, und finde immer mehr Merkwürdigkeiten:

Code: Alles auswählen

>>> class A(object):
...     def __call__(self): print "Called"
...
>>> class B(object):
...     pass
...
>>> A()
<__main__.A object at 0x2abb06796990>
>>> A.__call__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method __call__() must be called with A instance as first argument (got nothing instead)
>>> B()
<__main__.B object at 0x2abb06796f50>
>>> B.__call__()
<__main__.B object at 0x2abb06796c10>
Also ist das seltsame Verhalten von special methods zwingend notwendig, damit Klassen funktionieren. Ist das ein Design-Fehler und kann man den bei Python 4 geradebiegen? Aber wie?
BlackJack

@Sirius3: Was ist daran denn jetzt merkwürdig? Ist das nicht genau das was hier die ganze Zeit besprochen wird? Dachte ich zumindest bis jetzt. Oder stolperst Du gerade darüber das der Punktoperator tatsächlich erst in dem Objekt nachsieht auf dem er aufgerufen wurde? Ich glaube das hat niemand bezweifelt. Bis jetzt. :-)
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@BlackJack: bisher habe ich daran gezweifelt, dass das Verhalten von special methods anders sein muß, wie das von normalen Methoden, also daß es nicht nur ein Relikt aus alten Zeiten ist. Aber anscheinend funktioniert Python a nur wenn sich special-methods genau so verhalten, wie sie sich jetzt verhalten, daß man also gar keine Konsistenz erreichen kann. Das finde ich schade, weil ich gerne einheitliches Verhalten in Bezug auf Programmierung habe.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Python könnte man problemlos so ändern dass sich Special Methods genauso wie normale Methoden Verhalten.

In jedemfall würde ich aber nicht versuchen hier einen tieferen Sinn zu suchen der einfach nicht (mehr) existiert -- sofern er jemals existiert hat. Python ist halt nicht nach einer Eingebung Guidos in Code gegossen worden, sondern hat sich über einen sehr langen Zeitraum entwickelt, da bleiben einige historische Artefakte und Inkonsistenzen nicht aus. Die stdlib zeigt ja nur zu deutlich wieviele WTFs die Python Entwickler zu produzieren im Stande sind.
BlackJack

@DasIch: Wo wird denn dann ent-/unterschieden ob die Methode für ein Exemplar oder die Klasse gelten soll?
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@BlackJack: Klassenmethoden müssten dann in der Metaklasse definiert werden. Metaklassenmethoden müssten dann allerdings in Metametaklassen definiert werden...
In specifications, Murphy's Law supersedes Ohm's.
BlackJack

@pillmuncher: Aber genau so funktioniert das doch *jetzt* schon‽ Wenn ich auf einer Klasse zum Beispiel `__repr__()`-Abfrage dann bekomme ich nicht das Attribut von der Klasse, sondern das von deren Metaklasse. Genau das soll man ja angeblich abschaffen können. Sehe ich irgendwie nicht.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@BlackJack: DasIch schrieb, man könne Python so ändern, dass sich Special Methods ebenso verhalten wie andere Methoden. Du fragtest daraufhin, wie man dann Exemplar- von Klassenmethoden unterscheidne könnte, und meintest vermutlich mit Klassenmethoden "Methoden, die auf dem Exemplar operieren, aber in __class__.__dict__ leben". Das Wort Klassenmethode habe ich als classmethod gelesen, und das ist natürlich was anderes. Die leben zwar auch in __class__.__dict__, werden aber mit der Klasse selbst als erstem Argument aufgerufen. Da erzähle ich dir nichts neues. Wollte man den Special Method Lookup ändern, würde sich da tatsächlich nichts ändern. Die Frage ist allerdings, wozu man classmethod eigentlich braucht, wenn man Metaklassen hat? Momentan kann man Klassen-Attribute auch über Exemplare erreichen, allerdings keine Metaklassen-Attribute.

Änderte man den Lookup-Mechanismus so, dass auch bei Special Methods zuerst im __dict__ des Exemplars und - falls dort nicht vorhanden - in __class__.__dict__ nachgesehen wird, dann wäre das genauso wie bei anderen Attributen auch und man könnte Exemplar- und Klassenmethoden leicht unterscheiden.

Irgendwie ist mir nicht recht klar, über was wir gerade reden. Ist auch schon spät.
In specifications, Murphy's Law supersedes Ohm's.
BlackJack

@pillmuncher: Ich kann dem irgendwie nicht folgen. Irgendwo muss man IMHO immer etwas ”unregelmässiges” machen. Vielleicht gehen wir mal von Methoden weg und reden einfach nur über Attribute. Auf einem Objekt nachschauen und wenn es dort nicht vorhanden ist, auf der Klasse nachschauen, ist das ”normale” Verhalten. Das geht aber bei ”Spezialattributen” nicht. Wenn ich ein `__repr__` vom Exemplar abfrage, bekäme ich die gebundene Methode. Okay. Wenn ich `__repr__` von der Klasse abfrage, bekäme ich die ungebundene Methode. Nicht gut, denn damit bekomme ich ja keine Repräsentation der Klasse, was ja aber haben will wenn ich ``repr(Klasse)`` aufrufe. Also werden Spezialattribute gleich in der Klasse gesucht. Auf dem Exemplar bekomme ich `__repr__` von der Klasse. Okay, die ist ja dazu da ein Exemplar umzuwandeln. Und auf der Klasse bekomme ich das `__repr__` von der Metaklasse. Auch richtig, denn das ist ja geschrieben um die Klasse umzuwandeln. Wie sollte man das anders lösen ohne an anderer Stelle dafür ”komisches Spezialverhalten” einzuführen?
Antworten