Seite 1 von 2

Verfasst: Sonntag 26. April 2009, 21:20
von jerch
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'>

Verfasst: Sonntag 26. April 2009, 21:40
von Trundle
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.

Verfasst: Montag 27. April 2009, 19:18
von Goswin
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.

Verfasst: Montag 27. April 2009, 23:27
von jerch
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.

Verfasst: Dienstag 28. April 2009, 09:25
von EyDu
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.

Verfasst: Dienstag 28. April 2009, 09:50
von Goswin
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.

Verfasst: Dienstag 28. April 2009, 10:48
von EyDu
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

Verfasst: Dienstag 28. April 2009, 17:17
von jerch
@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.

Verfasst: Dienstag 28. April 2009, 18:09
von b.esser-wisser
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

Verfasst: Dienstag 28. April 2009, 18:31
von Darii
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.

Verfasst: Dienstag 28. April 2009, 21:23
von EyDu
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.