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

jerch hat geschrieben:Fraglich ist noch, ob das Verhalten nur für direkte Typnachfolger von Metaklassen oder für Klassentypen generell gilt.
...
Habs mal kurz getestet, scheint tatsächlich generell für Klassentypen zu gelten.
EyDu hat geschrieben:Hier wird gar nichts unterschiedlich behandelt. Erst werden die Attribute im Objekt gesucht, dann in den Klassen entsprechend der Vererbungshierarchie.

Ich würde jetzt im Nachhinein auch sagen, das Verhalten gilt ganz allgemein, und je länger ich darüber nachdenke, desto logischer kommt es mir vor. Ein ganz einfaches Beispiel (ohne Metaklassen) wäre:

Code: Alles auswählen

class K(object):
  foo = lambda oK,verwirf: "Klassenfunktion"
#
oK = K()
oK.foo = lambda verwirf: "Objektfunktion"


#"Objektsuche VOR Klassensuche:
print oK.foo(None)  #druckt "Objektfunktion" aus
Da Klassen selber "nur" von Metaklassen erzeugte Objekte sind, gilt parallel zum obigen Beispiel auch das Beispiel:

Code: Alles auswählen

class mK(type):
  foo = lambda K,verwirf: "Klassenfunktion"
#
class K(object):
  __metaclass__ = mK
  foo = lambda verwirf: "Objektfunktion"
#
oK = K()


#"Objektsuche VOR Klassensuche:
print K.foo(oK)  #druckt "Objektfunktion" aus
Das einzige, was zu beachten ist, ist dass Klassenaufrufen (gleich welcher Art) immer eine Instanz der Klasse hinzuzufügen ist.
Zuletzt geändert von Goswin am Dienstag 28. April 2009, 13:48, insgesamt 3-mal geändert.
Python nervt. (Nicht immer, und natürlich nur mich, niemals andere)
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Vielleicht hilft euch das bei euren Spekulationen auf die Sprünge:

Code: Alles auswählen

>>> class Meta(type):
...     pass
...
>>> class Spam(object):
...     __metaclass__ = Meta
...
>>> s = Spam()
>>> type(s)
<class '__main__.Spam'>
>>> type(Spam)
<class '__main__.Meta'>
>>> isinstance(s, Spam)
True
>>> isinstance(s, Meta)
False
Das Leben ist wie ein Tennisball.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@EyDu:
Naja, das war schon klar.

Der Hund liegt trotzdem in der Typenhierarchie begraben, für Klassen findet Python das Metaklassenattribut via type(Klasse).__dict__, für Objekte dagegen nicht (das Metaklassenattribut wäre erst mit type(type(obj)) zu finden).
In meinen Augen ist das schon ein Unterschied in der Behandlung, zumal die Dokumentation die Metaklassen explizit rausnimmt (allerdings wird auch nur von objects geredet). Ob dieser Unterschied nun hard kodiert ist oder strukturell bedingt ist, ist für mich als Anwender der Sprache egal, da die Phänomenologie entscheidend ist.
Benutzeravatar
b.esser-wisser
User
Beiträge: 272
Registriert: Freitag 20. Februar 2009, 14:21
Wohnort: Bundeshauptstadt B.

Welcher Hund denn nun wieder?
Attribute werden in
a) Instanzen
b) Klassen
c) Eltern-klassen
gesucht:

Code: Alles auswählen

# Beispiel zur Verwirrung
class Meta(type):
    obscure_attr = "In Meta"
class Foo(object):
    foo_attr = "In Foo"
class Bar(Foo):
    bar_attr = "In Bar"
class Bar2(object):
    bar_attr = "In Bar2"
class Baz(Bar, Bar2):
    __metaclass__ = Meta
    baz_attr = "In Baz"
    def __init__(self):
        self.inst_attr="In baz-instance"
a_single_instance = Baz()
#geht:
print a_single_instance.inst_attr #logisch
print a_single_instance.baz_attr
print a_single_instance.foo_attr
#geht nicht:
try:
    print a_single_instance.obscure_attr
except AttributeError, ex:
    print ex.message
#geht:
print Baz.obscure_attr
# DENN: Baz IST eine Instanz von Meta()!
print isinstance(Baz, Meta)
hth, Jörg
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

jerch hat geschrieben:Der Hund liegt trotzdem in der Typenhierarchie begraben,
Ja natürlich liegt das an der Typhierarchie. Das sich zwei Objekte die unterschiedliche Attribute/Typen/etc. haben sich unterschiedlich verhalten sollte eigentlich niemanden Verwundern. Und du solltest auch nicht zwischen Objekten und Klassen unterscheiden sondern lieber zwischen Instanzen und Klassen, Objekte sind nämlich beide.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Und? Da ist immer noch nichts magisches.

Ich mach es mal noch ein wenig einfacher:

Code: Alles auswählen

>>> class Meta(type):
	pass

>>> class Spam(object):
	__metaclass__ = Meta

	
>>> Spam.__base__
<type 'object'>
Metaklasen kommen nicht in der Klassenhierarchie vor.
Das Leben ist wie ein Tennisball.
Antworten