Ungebunden kommt vor Gebunden

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
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

Ich habe durch mühseliges Probieren folgendes herausgefunden. Es sei

Code: Alles auswählen

class mK(type):
  def foo(self,*verwirf): return "gebunden"
#
class K(object):
  __metaclass__ = mK
  def foo(self): return "ungebunden"
#
oK = K()
Bei einem gebundenen Aufruf "K.foo(oK)" (Referenz von foo wird als Instanz interpretiert) wird foo in der Metaklasse_mK gesucht und oK nicht weiter ueberprueft.
Bei einem ungebundenen Aufruf "K.foo(oK)" (Referenz von foo wird als Klasse interpretiert) wird foo in der Klasse_K gesucht und ueberprueft, ob oK eine Instanz von K ist.

Aber es kann ja nicht beides gleichzeitig sein, und wie wird der Aufruf nun wirklich behandelt?

Code: Alles auswählen

print K.foo(oK) #???
OK, der Aufruf druckt hier "ungebunden" aus. Aber warum das so ist, ist mir schleierhaft, und gut dokumentiert scheint das nicht zu sein. Falls die Methode in Klasse_K fortkommentiert wird, dann wird plötzlich "gebunden" ausgedruckt.
Zuletzt geändert von Goswin am Dienstag 28. April 2009, 13:38, insgesamt 2-mal geändert.
Python nervt. (Nicht immer, und natürlich nur mich, niemals andere)
Benutzeravatar
b.esser-wisser
User
Beiträge: 272
Registriert: Freitag 20. Februar 2009, 14:21
Wohnort: Bundeshauptstadt B.

Goswin hat geschrieben:Bei einem gebundenen Aufruf "K.foo(oK)", wird foo in der Metaklasse_mK gesucht und oK nicht weiter ueberprueft.
Nein, das ist nicht der Fall - es wird nur in K gesucht. Und wenn du gleich "oK.foo()" aufrufst wird sogar zuerst in "oK" gesucht, falls das nicht mit "__slots__" abgestellt wurde.
Ich verstehe nicht so ganz, was du da machst/rätst/willst/beobachtest.

hth, Jörg
edit: typo(s)
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

Mein Tipp wäre es ja, Metaklassen nicht zu verwenden, wenn man sich das Verhalten nicht erklären kann, weil man sie dann eh nicht braucht.

Und ansonsten ist es eben hilfreich, nicht einfach wild herumzuraten, sondern sich klarzumachen, was eigentlich passiert. Und der genaue Code, wie du was getestet hast, wäre auch nicht ganz verkehrt, denn aus der Beschreibung werde ich nicht so wirklich schlau.

Warum das auf einmal eine "gebundene" Methode ist, wenn man die Methodendeklaration in `K` weglässt, sollte doch eigentlich klar sein. Eine Klasse ist einfach das Exemplar der Metaklasse. Folglich würde in deinem Beispiel gelten: "K = mK()". Und dann sollte auch klar sein, warum die Methode "gebunden" ist (bei auskommentiertem `foo()`). Also so viel Magie und unerklärlich ist das eigentlich gar nicht, es sei denn, ich habe dich völlig falsch verstanden.
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

Trundle hat geschrieben:Mein Tipp wäre es ja, Metaklassen nicht zu verwenden, wenn man sich das Verhalten nicht erklären kann.
Ach, du hast ja so was von recht! *ironisch_grins*
Deshalb verwende ich sie ja auch nicht, sondern teste damit herum, um mir klarzumachen, was da eigentlich passiert.
Trundle hat geschrieben:der genaue Code, wie du was getestet hast, wäre auch nicht ganz verkehrt, denn aus der Beschreibung werde ich nicht so wirklich schlau..
In meinem obigen Beitrag befindet sich 100% des Codes, den ich benutzt habe.
Trundle hat geschrieben: ...es sei denn, ich habe dich völlig falsch verstanden.
Das kommt mir auch so vor. Aber mir fällt *ganz_ehrlich* nicht ein, wie ich mich besser erklären kann.


( :-) Ich erwarte vom Forum NICHT, dass es mir meine Probleme löst, sondern mein Metaproblem. Meine Probleme sollten ja MEINE Arbeit sein; mein Metaproblem ist, Python zu lernen :-) )
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

Ehrlich gesage verstehe ich genau wie b.esser-wisser nicht, was dein Problem ist. Es wird beide Male in K gesucht.
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

Darii hat geschrieben:Ehrlich gesage verstehe ich genau wie b.esser-wisser nicht, was dein Problem ist. Es wird beide Male in K gesucht.
Soweit ich Python bisher verstanden habe, ist der Ausdruck "K.foo(oK)" mehrdeutig, und in einer Programmiersprache sind Ausdrücke in der Regel eindeutig definiert oder fehlerbehaftet. Ich versuche ganz einfach herauszufinden, wie der Ausdruck "K.foo(oK)" im obigen Kontext von Python ausgelegt wird. Anders gesagt, aufgrund von welchen Überlegungen kann ich voraussagen, dass das obige Programm "ungebunden" und nicht etwa "gebunden" ausgibt?
Benutzeravatar
b.esser-wisser
User
Beiträge: 272
Registriert: Freitag 20. Februar 2009, 14:21
Wohnort: Bundeshauptstadt B.

Goswin hat geschrieben:Soweit ich Python bisher verstanden habe, ist der Ausdruck "K.foo(oK)" mehrdeutig,
Wie kommst du darauf?
Da steht "nimm das Attribut 'foo' von 'K' und rufe es mit dem Parameter 'oK' auf". Hier kommt evtl Die Reihenfolge ins Spiel, wie Python nach Attributen/Methoden sucht - das hat aber höchstens mit der (den) Elternklasse(n), aber nicht mit der Metaklasse zu tun. und bei "suchen", genauer: beim "nachschlagen" (engl. "look up") nimmt Python den ersten Treffer (vgl.: die Reihenfolge bei mehreren Elternklassen "class Foo(Bar, Baz, Boom, etc, pp):").
Goswin hat geschrieben:Ich versuche ganz einfach herauszufinden, wie der Ausdruck "K.foo(oK)" im obigen Kontext von Python ausgelegt wird.Anders gesagt, aufgrund von welchen Überlegungen kann ich voraussagen, dass das obige Programm "ungebunden" und nicht etwa "gebunden" ausgibt?
Deine Bezeichungen "gebunden" bzw. "ungebunden" sind hier sehr ungünstig gewählt, weil Python die englischen Worte dafür ein bisschen anders verwendet (s. Paste).
Allerdings ist der Ausdruck eindeutig - Mehrdeutigkeit ist ja normalerweise ein Fehler - dein Code wird nie "gebunden" ausgeben, weil Attribute von Instanzen nie in der Metaklasse gesucht werden.

Und ich denke nicht, dass ein tiefes Verständnis von Metaklassen zum normalen Pythonwissen gehört 8)
Ich halte es da mit
Tim Peters hat geschrieben: "Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don't."

—Tim Peters
"[...]wenn du dich fragst, ob du [Metaklassen] brauchst, brauchst du sie nicht" ;)

hth, Jörg
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

Goswin hat geschrieben:Soweit ich Python bisher verstanden habe, ist der Ausdruck "K.foo(oK)" mehrdeutig, und in einer Programmiersprache sind Ausdrücke in der Regel eindeutig definiert oder fehlerbehaftet.
Der Ausdruck ist insofern mehrdeutig, als das Python dynamisch typisiert ist und es von den Konkreten Typen von K und oK abhängt was passiert. Er ist insofern eindeutig als das festgelegt ist wie dieser Ausdruck behandelt wird, was genau dabei passiert hatte sma hier mal sehr schon dargelegt)
Ich versuche ganz einfach herauszufinden, wie der Ausdruck "K.foo(oK)" im obigen Kontext von Python ausgelegt wird.
Er ist in dem Fall identisch zu oK.foo()
Anders gesagt, aufgrund von welchen Überlegungen kann ich voraussagen, dass das obige Programm "ungebunden" und nicht etwa "gebunden" ausgibt?
Warum kommst du überhaupt auf die Idee, dass da überhaupt "gebunden" ausgegeben werden sollte.

Edit: Ich glaube worauf du hinauswillst ist folgender Fall, oder?

Code: Alles auswählen

class mK(type): 
  def foo(self,*verwirf): return "gebunden" 
# 
class K(object): 
  __metaclass__ = mK 
# 
oK = K()
print K.foo(oK) # gebunden
print oK.foo() # AttributeError
http://docs.python.org/reference/datamodel.html#invoking-descriptors hat geschrieben:The default behavior for attribute access is to get, set, or delete the attribute from an object’s dictionary. For instance, a.x has a lookup chain starting with a.__dict__['x'], then type(a).__dict__['x'], and continuing through the base classes of type(a) excluding metaclasses.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Oder:

Code: Alles auswählen

class mK(type): 
   def foo(self,*verwirf): return "gebunden" 

class K(object): 
   __metaclass__ = mK

print K.foo()   # gebunden
print mK.foo(K) # gebunden
Das K.foo(oK) geht nur, da oK in den *verwirf Argumenten landet.

Edit:
Oder suchst Du eher was zur dynamischen Klassengenerierung? Dafür sind Metaklassen ganz brauchbar:

Code: Alles auswählen

def foo1(instance, greeting):
	return greeting

def foo2(instance, num):
	return ' '.join(instance.piep() for i in range(num))

def foo3(instance, num, greet):
	return ' '.join(instance.foo1(greet) for i in range(num))

class mK(type):
	def __init__(self, *args):
		self.foo1 = foo1
		self.foo2 = foo2
		self.foo3 = foo3

class K(object):
	__metaclass__ = mK
	def piep(self):
		return 'Piep'

instance = K()
print instance.foo1('hello') # hello
print instance.foo2(3)       # Piep Piep Piep
print instance.foo3(3, 'hello')       # hello hello hello
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

@jerch: Das ist aber einfach nur eine Attributzuweisung, das kann man wenn man will auch in der __init__ der Klasse machen. Oder auch einfach so (wobei das je nachdem wann es geschieht Monkey Patching ist, siehe Forensuche). Außerdem rufst du in deiner Metaklasse nicht die __init__ von type auf.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Das ist aber einfach nur eine Attributzuweisung, das kann man wenn man will auch in der __init__ der Klasse machen. Oder auch einfach so (wobei das je nachdem wann es geschieht Monkey Patching ist, siehe Forensuche)
Einfache Beispiele zur Veranschaulichung von OOP-Konzepten sind meistens fragwürdig. Python bietet viel Spielraum hinsichtlich der Umsetzung, zwischen einfachen Funktiondefinitionen bis hin zu Metaklassen führen viele Wege nach Rom. (Man könnte ja auch komplett auf das class-Konstrukt verzichten und alles in einer cleveren Schachtelung von Funktionen und Modulen vornehmen usw.)
Die Klasse einer Klasse braucht man halt noch seltener bzw. können solche Problemstellungen anders gelöst werden. Allerdings sind Sachen wie Prototypen, Interfaces oder Fabrikklassen mit Metaklassen elegant umsetzbar. Und in diese Richtung wollte das Bsp. zeigen.
Außerdem rufst du in deiner Metaklasse nicht die __init__ von type auf.
Die type-Initialisierung ist hier irrelevant, da die Klassensignatur nicht geändert wird sondern nur die Instanzen um Methoden erweitert werden.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

jerch hat geschrieben:Die type-Initialisierung ist hier irrelevant, da die Klassensignatur nicht geändert wird sondern nur die Instanzen um Methoden erweitert werden.
Natürlich wird die Klasse geändert, dazu ist die __init__-Methode der Metaklassen da. Eine Instanz von K wird erst mit der __call__-Methode der Metaklasse erzeugt und ist daher noch gar nicht bekannt. Und um dies nicht zu verwechseln schreibt man innerhalb der Metaklassen nicht "self" sonder "cls".
Das Leben ist wie ein Tennisball.
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Die type-Initialisierung ist hier irrelevant, da die Klassensignatur nicht geändert wird sondern nur die Instanzen um Methoden erweitert werden.
Du überschreibst doch den Initialisator von type. Da type eine recht komplexe Klasse ist, gehe ich davon aus, das __init__ einige Dinge tun wird. Zumindest hab ich in der Doku noch keine genaue Beschreibung von type gefunden, aber man kann es annehmen, eben wegen der Komplexität. Insofern sollte man immer __init__ einer Superklasse aufrufen, wenn man nicht genau weiß, was man tut. Oder weißt du *genau*, das type.__init__ nichts macht? Wenn ja, dann kann man es weglassen, ich schätze die Möglichkeit als sehr gering ein.

Zu dem Zitat an sich nun: Das ergibt doch so keinen Sinn. Wenn du explizit eine Methode in einer Unterklasse mit gleichen Namen einer Methode der Superklasse anlegst, wird die ursprüngliche Methode bei Aufruf nicht implizit aufgerufen. Deswegen sollte man sie immer explizit aufrufen (wenn man nicht genau weiß, was man tut <- da brauch man mal eine Abkürzung für). Ausnahme ist object, dessen __init__ sowieso nichts macht. In Python gibt es keine magischen Verhaltensweisen, und erst recht nicht sowas wie C++ Polymorphie, wo sich C++ bei überladenen Methoden immer die mit der passenden Signatur heraussucht (solltest du sowas in der Art gemeint haben, fiel mir dazu ein)
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

@EyDu: Die Klasse wird eher durch die `__new__`-Methode der Metaklasse erzeugt, nicht durch die `__call__`-Methode, da bei Python die "special methods" eigentlich immer im Typ eines Objekts nachgeschaut werden. `Spam()` ist also nicht `Spam.__call__()`, sondern `type(Spam).__call__()`. Und der Typ einer Metaklasse ist eben idR `type` und `type.__call__` ruft dann die `__new__`-Methode auf.
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

type.__call__ ruft normalerweise __new__ auf (ohne __metaclass__ macht das type, da das die "Standardmetaklasse" ist bzw. ein super(MetaKlasse, cls).__call__()), wobei __new__ eine neue Instanz alloziert und __init__ diese initialisiert. Daher verwendet man in __new__ den cls-Ausdruck um zu zeigen, daß es sich hier noch um die Klasse selbst handelt (die Instanz wird erst mit dem return erzeugt), und in __init__-self als Instanzanzeiger. Bei Metaklassen gibts keine einheitliche Empfehlung, da in __new__ nicht mal cls richtig wäre, sondern erst in __init__, anders betrachtet kann man hier getrost cls und self weiter verwenden, da die Zielklasse eben eine Instanz der Metaklasse ist.

Zum type.__init__():
Das gibts überhaupt erst seit 2 Jahren und macht nicht viel mehr, als Signaturänderungen zwischen __new__ und __init__ abzufangen.
In P3Y soll hier wohl ein bisschen mehr passieren (strengere Prüfung auf Parameter etc., in 2.6 teilweise umgesetzt), daher geht Guidos Empfehlung zu super(MetaKlasse, cls).__init__(name, base, attr).
Ergo ist es mit zweifelhaftem type.__init__ besser als ohne, da auch zukünftig von Bedeutung. Insofern hast Du Recht.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Um etwas Licht in die Sache zu bringen:

Code: Alles auswählen

class mK(type):
    print "Erzeuge MetaKlasse mK"
    def __new__(cls, *args):
        print "mK: __new__", type(cls)
        return type.__new__(cls, *args)
    def __init__(cls, *args):
        print "mK: __init__", type(cls)
        super(mK, cls).__init__(*args)
    def __call__(cls, *args):
        print "mK: __call__", type(cls)
        return super(mK, cls).__call__(*args)

print mK

class K(object):
    print "Erzeuge Klasse K, instantiiert von mK"
    __metaclass__ = mK
    def __new__(cls, *args):
        print "K: __new__", type(cls)
        return object.__new__(cls, *args)
    def __init__(self):
        print "K: __init__", type(self)

print K

print 'Erzeuge Instanz, ruft __call__ von mK auf'
instance = K()
print instance
Ausgabe ist diese:

Code: Alles auswählen

Erzeuge MetaKlasse mK
<class '__main__.mK'>
Erzeuge Klasse K, instantiiert von mK
mK: __new__ <type 'type'>
mK: __init__ <class '__main__.mK'>
<class '__main__.K'>
Erzeuge Instanz, ruft __call__ von mK auf
mK: __call__ <class '__main__.mK'>
K: __new__ <class '__main__.mK'>
K: __init__ <class '__main__.K'>
<__main__.K object at 0x80a494c>
Edit: Typen ergänzt.
Wegen cls vs. self: Ich nutze in Metaklassen eigentlich immer cls, korrekterweise müßte Metaklasse.__new__ eine andere Nomenklatur bekommen (hab Vorschläge wie 'mcls', 'meta' oder einfach 'm' gesehen).

Edit2:
Aus Sicht der Zielklasse ändert sich der Typ, wie man schön im Vgl. zu diesem Bsp. sehen kann:

Code: Alles auswählen

class Husten(object):
    def __new__(cls, *args):
        print "Husten: __new__", type(cls)
        return object.__new__(cls, *args)
    def __init__(self):
        print "Husten: __init__", type(self)

Husten()
#--->
# Husten: __new__ <type 'type'>
# Husten: __init__ <class '__main__.Husten'>
Zuletzt geändert von jerch am Sonntag 26. April 2009, 21:54, insgesamt 1-mal geändert.
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

Ja, mein Beitrag war natürlich quatsch, da ich mich verlesen hatte. Ich beziehe mich natürlich nicht auf das Erzeugen eines Exemplars von `K`, sondern auf das Erzeugen von `K` an sich durch die Metaklasse, was ja aber EyDu gar nicht gemeint hatte.
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

Darii hat geschrieben:Ich glaube worauf du hinauswillst ist folgender Fall, oder?
... schnipp ...
http://docs.python.org/reference/datamodel.html#invoking-descriptors hat geschrieben:The default behavior for attribute access is to get, set, or delete the attribute from an object’s dictionary. For instance, a.x has a lookup chain starting with a.__dict__['x'], then type(a).__dict__['x'], and continuing through the base classes of type(a) excluding metaclasses.
In der Tat scheint das die Situation zu beschreiben und löst die Mehrdeutigkeit auf, indem es den Metaklassen eine niedrigere Priorität als den Klassen einräumt. Der Themenstrang müsste vielleicht heißen "Attributsuche kommt vor Klassensuche wenn beides möglich ist" oder "Objektbindung kommt vor Klassenbindung wenn beides möglich ist".

Mein Beispiel dürfte natürlich extrem selten sein, und ist extra von mir so hingebogen, dass beide Alternativen möglich sind. Sonst kann ich ja nichts über die Prioritäten von Python herausfinden.
Python nervt. (Nicht immer, und natürlich nur mich, niemals andere)
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Python behandelt hier wohl Objekt- und Klassentypen unterschiedlich. Für das Objekt wird das Attribut nicht in der Metaklasse nachgeschlagen (oK.foo wird mit Fehler quittiert) während für die Klasse vom Typen der Metaklasse der type(Klasse).__dict__-Aufruf die Methode foo von der Metaklasse findet. Da Du mit der Klasse agierst (K.foo(obj)) und nicht mit dem Objekt (obj.foo()) schlägt sich die unterschiedliche Typenbehandlung durch (für den Fall, das in K foo nicht gesetzt ist).

Fraglich ist noch, ob das Verhalten nur für direkte Typnachfolger von Metaklassen oder für Klassentypen generell gilt. Hierzu müßte man sich mal die Implementation der "look-up-Vermeidung" von Metaklassen anschauen.

Edit:
Habs mal kurz getestet, scheint tatsächlich generell für Klassentypen zu gelten, da eine abgeleitete Klasse den Metaklassentyp erbt.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hier wird gar nichts unterschiedlich behandelt. Erst werden die Attribute im Objekt gesucht, dann in den Klassen entsprechend der Vererbungshierarchie. In dieser Hierarchie tauchen Metaklassen aber per Definition nicht auf, deshalb gibt es nichts extra zu behandeln. Es wäre auch sehr unpraktisch, wenn man beim Erzeugen einer Klasse auch noch dessen Namespace mit den erzeugenden Funktionen zumüllt.
Das Leben ist wie ein Tennisball.
Antworten