Klassen und die dynamische Erzeugung von Klassen

Beziehung zwischen "class" und "type"

cogwheels or behind the scenes In diesem Kapitel möchten wir Sie etwas tiefer mit dem magischen Verhalten von Python vertraut machen, wenn wir eine Klasse definieren oder eine Instanz einer Klasse erstellen. Möglicherweise fragen Sie sich: "Muss ich die Details wirklich wissen?" Vermutlich nicht...oder Sie gehören zu denen, die Klassen auf einem fortgeschrittenem Niveau erstellen.

Zunächst konzentrieren wir uns auf die Verbindung zwischen "type" und "class". Als Sie eine Klasse definiert haben, haben Sie sich vielleicht gefragt, was hinter den "Zeilen" passiert. Wir haben bereits gesehen, dass type(object) die Klasse der Instanz zurückliefert:
x = [4, 5, 9]
y = "Hello"
print(type(x), type(y))
Wir erhalten folgende Ausgabe:
<class 'list'> <class 'str'>
Wenn type() auf die Namen der Klassen selbst angewendet werden, so erhalten wir "type" als Ergebnis.
print(type(list), type(str))

Output:
<class 'type'> <class 'type'>
Das gleiche Ergebnis erhalten wir mit folgendem Code:
x = [4, 5, 9]
y = "Hello"
print(type(x), type(y))
print(type(type(x)), type(type(y)))

Output:
<class 'list'> <class 'str'>
<class 'type'> <class 'type'>
Eine selbst erstellte Klasse (oder die Klase object) ist eine Instanz des Objekts "type", welche selbst eine Klasse ist.

Sprich...Klassen werden durch type erstellt.
In anderen Worten: Eine Klasse ist eine Instanz der Klasse "type". In Python3 gibt es keine Unterschied "Klassen" und "Typen". In den meisten Fällen werden Sie synonym verwendet.

Die Tatsache, dass Klassen Instanzen der Klasse "type" sind, erlaubt uns Meta-Klassen zu programmieren. Wir können Klassen kreieren, die von der Klasse "type" erben. Somit ist eine Meta-Klasse eine Subklasse der Klasse "type".

Statt eines einzigen Arguments, kann type mit drei Parametern aufgerufen werden:
type(classname, superclasses, attributes_dict)
Wenn type mit drei Argumenten aufgerufen wird, so liefert es ein neues type-Objekt zurück. Das bietet uns eine dynamische Form des Klassen-Statements. Schauen wir uns eine einfache Klassen-Definition an:
class A:
    pass
x = A()
print(type(x))
Wir erhalten folgende Ausgabe:
<class '__main__.A'>
Wir können type() ebenfalls für die oben beschriebene Klassen-Definition verwenden:
A = type("A", (), {})
x = A()
print(type(x))
Wir erhalten ebenfalls folgende Ausgabe:
<class '__main__.A'>
Generell bedeutet das, dass wir die Klasse A mit
type(classname, superclasses, attributedict)
definieren können.
Wenn wir type() aufrufen, wird die call-Methode von type aufgerufen. Die call-Methode ruft zwei weitere Methoden auf: new und init.
type.__new__(typeclass, classname, superclasses, attributedict)
type.__init__(cls, classname, superclasses, attributedict)
Die new-Methode erstellt und liefert das neue Klassen-Objekt zurück. Anschliessend initialisiert die init-Methode das erstellte Objekt.
class Robot:
    counter = 0
    def __init__(self, name):
        self.name = name
    def sayHello(self):
        return "Hi, I am " + self.name
	def Rob_init(self, name):
    	self.name = name

Robot2 = type("Robot2", 
              (), 
              {"counter":0, 
               "__init__": Rob_init,
               "sayHello": lambda self: "Hi, I am " + self.name})

x = Robot2("Marvin")
print(x.name)
print(x.sayHello())
y = Robot("Marvin")
print(y.name)
print(y.sayHello())
print(x.__dict__)
print(y.__dict__)
Nach Ausführung des Codes erhalten wir folgende Ausgabe:
Marvin
Hi, I am Marvin
Marvin
Hi, I am Marvin
{'name': 'Marvin'}
{'name': 'Marvin'}
Die Klassen-Definitionen von Robot und Robot2 sind syntaktisch völlig verschieden. Aber logisch gesehen, implementieren beide genau das Gleiche.

Was Python im ersten Beispiel macht, ist, dass alle Methoden und Attribute der Klasse Robot gesammelt werden um sie dann als Parameter dem Argument attributes_dict beim type-Aufruf zu übergeben. Python macht also das gleiche, wie wir es mit der Klasse Robot2 gemacht haben.