Klassen als Objekte
Bevor Sie Metaklassen verstehen, müssen Sie die Klassen in Python beherrschen. Und Python hat eine sehr eigentümliche Vorstellung davon, was Klassen sind, die der Sprache Smalltalk entlehnt ist.
In den meisten Sprachen sind Klassen nur Codestücke, die beschreiben, wie man ein Objekt erzeugt. Das ist auch in Python irgendwie wahr:
>>> class ObjectCreator(object):
... pass
...
>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>
Aber Klassen sind in Python mehr als das. Klassen sind auch Objekte.
Ja, Objekte.
Sobald Sie das Schlüsselwort class
aus, führt Python es aus und erstellt eine objeto . Die Anweisung
>>> class ObjectCreator(object):
... pass
...
erzeugt im Speicher ein Objekt mit dem Namen ObjectCreator
.
Dieses Objekt (die Klasse) ist selbst in der Lage, Objekte (die Instanzen) zu erzeugen, und deshalb ist es eine Klasse .
Aber trotzdem ist es ein Objekt, und deshalb:
- können Sie sie einer Variablen zuweisen
- Sie können es kopieren
- können Sie ihm Attribute hinzufügen
- können Sie ihn als Funktionsparameter übergeben
z.B.:
>>> print(ObjectCreator) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
... print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>
Klassen dynamisch erstellen
Da es sich bei Klassen um Objekte handelt, können Sie sie wie jedes andere Objekt spontan erstellen.
Zunächst können Sie eine Klasse in einer Funktion erstellen, indem Sie class
:
>>> def choose_class(name):
... if name == 'foo':
... class Foo(object):
... pass
... return Foo # return the class, not an instance
... else:
... class Bar(object):
... pass
... return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>
Aber es ist nicht so dynamisch, da man die ganze Klasse immer noch selbst schreiben muss.
Da Klassen Objekte sind, müssen sie von etwas erzeugt werden.
Wenn Sie die class
Schlüsselwort, erstellt Python dieses Objekt automatisch. Aber wie wie bei den meisten Dingen in Python, gibt es auch eine Möglichkeit, dies manuell zu tun.
Erinnern Sie sich an die Funktion type
? Die gute alte Funktion, mit der Sie wissen, welchen Typ ein Objekt ist:
>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>
Nun, type
hat eine ganz andere Fähigkeit, es kann auch Klassen im laufenden Betrieb erstellen. type
kann die Beschreibung einer Klasse als Parameter annehmen, und eine Klasse zurückgeben.
(Ich weiß, es ist albern, dass ein und dieselbe Funktion zwei völlig unterschiedliche Verwendungszwecke haben kann, je nachdem, welche Parameter Sie ihr übergeben. Das ist ein Problem der Abwärtskompatibilität Kompatibilität in Python)
type
funktioniert folgendermaßen:
type(name, bases, attrs)
Wo:
name
Name der Klasse: Name der Klasse
bases
Tupel der Elternklasse (für Vererbung, kann leer sein)
attrs
Wörterbuch mit Namen und Werten der Attribute
z.B.:
>>> class MyShinyClass(object):
... pass
kann auf diese Weise manuell erstellt werden:
>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>
Sie werden feststellen, dass wir MyShinyClass
als Name der Klasse und als Variable, die die Klassenreferenz enthält. Sie können unterschiedlich sein, aber es gibt keinen Grund, die Dinge zu verkomplizieren.
type
akzeptiert ein Wörterbuch, um die Attribute der Klasse zu definieren. So:
>>> class Foo(object):
... bar = True
Kann übersetzt werden mit:
>>> Foo = type('Foo', (), {'bar':True})
Und als normale Klasse verwendet:
>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True
Und natürlich kann man sie auch vererben, also:
>>> class FooChild(Foo):
... pass
wäre:
>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True
Schließlich werden Sie Ihrer Klasse Methoden hinzufügen wollen. Definieren Sie einfach eine Funktion mit der richtigen Signatur und weisen Sie sie als Attribut zu.
>>> def echo_bar(self):
... print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True
Und Sie können noch mehr Methoden hinzufügen, nachdem Sie die Klasse dynamisch erstellt haben, genauso wie Sie Methoden zu einem normal erstellten Klassenobjekt hinzufügen.
>>> def echo_bar_more(self):
... print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True
Sie sehen, worauf wir hinauswollen: In Python sind Klassen Objekte, und Sie können eine Klasse spontan und dynamisch erstellen.
Das ist es, was Python tut, wenn Sie das Schlüsselwort class
und zwar unter Verwendung einer Metaklasse.
Was sind Metaklassen (endlich)
Metaklassen sind das "Material", aus dem Klassen entstehen.
Sie definieren Klassen, um Objekte zu erstellen, richtig?
Aber wir haben gelernt, dass Python-Klassen Objekte sind.
Nun, diese Objekte werden durch Metaklassen erzeugt. Sie sind die Klassen der Klassen, man kann sie sich so vorstellen:
MyClass = MetaClass()
my_object = MyClass()
Sie haben gesehen, dass type
können Sie so etwas tun:
MyClass = type('MyClass', (), {})
Das liegt daran, dass die Funktion type
ist in der Tat eine Metaklasse. type
ist die Metaklasse, mit der Python alle Klassen im Hintergrund erstellt.
Jetzt fragen Sie sich: "Warum zum Teufel ist es klein geschrieben und nicht Type
?"
Nun, ich denke, es ist eine Frage der Kohärenz mit str
, die Klasse, die die Strings-Objekte erstellt, und int
die Klasse, die Integer-Objekte erzeugt. type
ist nur die Klasse, die Klassenobjekte erzeugt.
Sie sehen das, indem Sie die __class__
Attribut.
Alles, und ich meine wirklich alles, ist in Python ein Objekt. Das schließt Ganzzahlen, Zeichenketten, Funktionen und Klassen. Sie alle sind Objekte. Und sie alle wurden wurden aus einer Klasse erstellt:
>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>
Nun, was ist die __class__
eines beliebigen __class__
?
>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>
Eine Metaklasse ist also nur das Material, das Klassenobjekte erzeugt.
Sie können es eine "Klassenfabrik" nennen, wenn Sie wollen.
type
ist die eingebaute Metaklasse, die Python verwendet, aber Sie können natürlich auch Ihre eigene eigene Metaklasse erstellen.
In Python 2 können Sie eine __metaclass__
Attribut, wenn Sie eine Klasse schreiben (siehe nächster Abschnitt für die Python 3-Syntax):
class Foo(object):
__metaclass__ = something...
[...]
Wenn Sie dies tun, wird Python die Metaklasse verwenden, um die Klasse Foo
.
Vorsicht, das ist knifflig.
Sie schreiben class Foo(object)
zuerst, aber das Klassenobjekt Foo
ist noch nicht erstellt noch nicht im Speicher angelegt.
Python sucht nach __metaclass__
in der Klassendefinition. Wenn er sie findet, wird er sie zur Erstellung der Objektklasse verwenden Foo
. Ist dies nicht der Fall, verwendet es type
um die Klasse zu erstellen.
Lesen Sie das mehrere Male.
Wenn Sie das tun:
class Foo(Bar):
pass
Python macht Folgendes:
Gibt es eine __metaclass__
Attribut in Foo
?
Wenn ja, erstellen Sie im Speicher ein Klassenobjekt (ich sagte Klassenobjekt, bleiben Sie bei mir) mit dem Namen Foo
indem wir das verwenden, was in __metaclass__
.
Wenn Python nicht finden kann __metaclass__
sucht es nach einer __metaclass__
auf der MODULE-Ebene und versuchen Sie, dasselbe zu tun (aber nur für Klassen, die nichts erben, im Grunde genommen Klassen alten Stils).
Wenn es dann nichts finden kann __metaclass__
überhaupt nicht, wird es die Bar
(der ersten übergeordneten) Metaklasse (die die Standard-Metaklasse sein kann) type
), um das Klassenobjekt zu erstellen.
Achten Sie dabei darauf, dass die __metaclass__
wird nicht vererbt, die Metaklasse der übergeordneten Klasse ( Bar.__class__
) sein wird. Wenn Bar
verwendet eine __metaclass__
Attribut, das die Bar
con type()
(und nicht type.__new__()
), werden die Unterklassen dieses Verhalten nicht erben.
Die große Frage ist nun, was kann man in __metaclass__
?
Die Antwort ist etwas, das eine Klasse schaffen kann.
Und was kann eine Klasse schaffen? type
oder irgendetwas, das es untergliedert oder verwendet.
Metaklassen in Python 3
Die Syntax zum Setzen der Metaklasse hat sich in Python 3 geändert:
class Foo(object, metaclass=something):
...
d.h. die __metaclass__
Attribut wird nicht mehr verwendet, stattdessen wird ein Schlüsselwortargument in der Liste der Basisklassen verwendet.
Das Verhalten von Metaklassen bleibt jedoch weitgehend identisch .
Eine Sache, die in Python 3 zu den Metaklassen hinzugekommen ist, ist, dass man auch Attribute als Schlüsselwort-Argumente an eine Metaklasse übergeben kann, etwa so:
class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
...
Lesen Sie im folgenden Abschnitt, wie Python dies handhabt.
Benutzerdefinierte Metaklassen
Der Hauptzweck einer Metaklasse besteht darin, die Klasse automatisch zu ändern, wenn sie erstellt wird.
Normalerweise tun Sie dies für APIs, wo Sie Klassen erstellen wollen, die der aktuellen Kontext passen.
Stellen Sie sich ein dummes Beispiel vor, in dem Sie entscheiden, dass alle Klassen in Ihrem Modul ihre Attribute in Großbuchstaben geschrieben haben sollen. Es gibt mehrere Möglichkeiten, dies dies zu tun, aber eine Möglichkeit ist, die __metaclass__
auf der Ebene der Module.
Auf diese Weise werden alle Klassen dieses Moduls unter Verwendung dieser Metaklasse erstellt, und wir müssen der Metaklasse nur sagen, dass sie alle Attribute in Großbuchstaben umwandeln soll.
Zum Glück, __metaclass__
kann eigentlich jedes Callable sein, es muss keine formale Klasse sein (ich weiß, etwas mit "Klasse" im Namen muss keine Klasse sein eine Klasse sein, stellen Sie sich vor... aber es ist hilfreich).
Wir beginnen also mit einem einfachen Beispiel, indem wir eine Funktion verwenden.
# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attrs):
"""
Return a class object, with the list of its attribute turned
into uppercase.
"""
# pick up any attribute that doesn't start with '__' and uppercase it
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in future_class_attrs.items()
}
# let `type` do the class creation
return type(future_class_name, future_class_parents, uppercase_attrs)
__metaclass__ = upper_attr # this will affect all classes in the module
class Foo(): # global __metaclass__ won't work with "object" though
# but we can define __metaclass__ here instead to affect only this class
# and this will work with "object" children
bar = 'bip'
Schauen wir nach:
>>> hasattr(Foo, 'bar')
False
>>> hasattr(Foo, 'BAR')
True
>>> Foo.BAR
'bip'
Nun wollen wir genau dasselbe tun, aber eine echte Klasse als Metaklasse verwenden:
# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
# __new__ is the method called before __init__
# it's the method that creates the object and returns it
# while __init__ just initializes the object passed as parameter
# you rarely use __new__, except when you want to control how the object
# is created.
# here the created object is the class, and we want to customize it
# so we override __new__
# you can do some stuff in __init__ too if you wish
# some advanced use involves overriding __call__ as well, but we won't
# see this
def __new__(upperattr_metaclass, future_class_name,
future_class_parents, future_class_attrs):
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in future_class_attrs.items()
}
return type(future_class_name, future_class_parents, uppercase_attrs)
Lassen Sie uns die obigen Zeilen neu schreiben, aber mit kürzeren und realistischeren Variablennamen, da wir jetzt wissen, was sie bedeuten:
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, attrs):
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in attrs.items()
}
return type(clsname, bases, uppercase_attrs)
Sie haben vielleicht das zusätzliche Argument bemerkt cls
. Es ist nichts Besonderes an sich: __new__
erhält immer die Klasse, in der sie definiert ist, als ersten Parameter. Genauso wie Sie self
für gewöhnliche Methoden, die die Instanz als ersten Parameter erhalten, oder die definierende Klasse für Klassenmethoden.
Aber das ist kein richtiges OOP. Wir rufen type
direkt und wir überschreiben oder rufen nicht die übergeordnete __new__
. Das sollten wir stattdessen tun:
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, attrs):
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in attrs.items()
}
return type.__new__(cls, clsname, bases, uppercase_attrs)
Wir können es noch sauberer machen, indem wir super
was die Vererbung erleichtern wird (denn ja, man kann Metaklassen haben, die von Metaklassen erben, die von Typ erben):
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, attrs):
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in attrs.items()
}
return super(UpperAttrMetaclass, cls).__new__(
cls, clsname, bases, uppercase_attrs)
Oh, und in Python 3, wenn Sie diesen Aufruf mit Schlüsselwort-Argumenten, wie dies tun:
class Foo(object, metaclass=MyMetaclass, kwarg1=value1):
...
Es wird in der Metaklasse so übersetzt, dass es verwendet werden kann:
class MyMetaclass(type):
def __new__(cls, clsname, bases, dct, kwargs1=default):
...
Das war's. Mehr gibt es über Metaklassen eigentlich nicht zu sagen.
Der Grund für die Komplexität des Codes, der Metaklassen verwendet, liegt nicht an der Metaklassen, sondern daran, dass man Metaklassen in der Regel verwendet, um verdrehte Dinge zu tun Introspektion, Manipulation von Vererbung, Variablen wie __dict__
, usw.
In der Tat sind Metaklassen besonders nützlich, um schwarze Magie zu betreiben, und daher komplizierte Dinge. Aber an sich sind sie einfach:
- eine Klassenbildung abfangen
- die Klasse ändern
- die geänderte Klasse zurückgeben
Warum sollten Sie Metaklassen anstelle von Funktionen verwenden?
Seit __metaclass__
jeden Callable akzeptieren kann, warum sollten Sie eine Klasse verwenden verwenden, da sie offensichtlich komplizierter ist?
Hierfür gibt es mehrere Gründe:
- Die Absicht ist klar. Wenn Sie lesen
UpperAttrMetaclass(type)
wissen Sie was folgen wird
- Sie können OOP verwenden. Metaklassen können von Metaklassen erben und übergeordnete Methoden außer Kraft setzen. Metaklassen können sogar Metaklassen verwenden.
- Unterklassen einer Klasse sind Instanzen ihrer Metaklasse, wenn Sie eine Metaklassen-Klasse angegeben haben, aber nicht mit einer Metaklassen-Funktion.
- Sie können Ihren Code besser strukturieren. Sie verwenden Metaklassen nie für etwas so Triviales wie das obige Beispiel. Normalerweise ist es für etwas Kompliziertes. Die Möglichkeit, mehrere Methoden zu erstellen und sie in einer Klasse zu gruppieren, ist sehr nützlich, um den Code leichter lesbar zu machen.
- Sie können einhaken
__new__
, __init__
y __call__
. Das erlaubt Ihnen, verschiedene Dinge zu tun, auch wenn Sie normalerweise alles in einer Woche erledigen können. __new__
, Manche Menschen fühlen sich einfach wohler, wenn sie __init__
.
- Diese heißen Metaklassen, verdammt noch mal! Das muss etwas bedeuten!
Warum sollten Sie Metaklassen verwenden?
Nun die große Frage. Warum sollten Sie eine obskure, fehleranfällige Funktion verwenden?
Nun, normalerweise nicht:
Metaklassen sind tiefere Magie, die 99 % der Benutzer sollten sich keine Gedanken darüber machen. Wenn Sie sich fragen, ob Sie sie brauchen, brauchen Sie sie nicht (die Leute, die sie tatsächlich brauchen, wissen mit Gewissheit, dass dass sie sie brauchen, und sie brauchen keine Erklärung, warum).
Python-Guru Tim Peters
Der Hauptanwendungsfall für eine Metaklasse ist die Erstellung einer API. Ein typisches Beispiel hierfür ist der Django ORM. Es erlaubt Ihnen, etwas wie dieses zu definieren:
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
Aber wenn Sie das tun:
person = Person(name='bob', age='35')
print(person.age)
Es wird nicht ein IntegerField
Gegenstand. Es gibt ein int
und kann sie sogar direkt aus der Datenbank übernehmen.
Dies ist möglich, weil models.Model
definiert __metaclass__
und wird ein Zauber angewendet, der die Person
die Sie gerade mit einfachen Aussagen definiert haben in eine komplexe Verknüpfung mit einem Datenbankfeld verwandeln.
Django lässt etwas Komplexes einfach aussehen, indem es eine einfache API zur Verfügung stellt und die Verwendung von Metaklassen, die den Code dieser API nachbilden, um die eigentliche Aufgabe hinter den Kulissen.
Das letzte Wort
Zunächst wissen Sie, dass Klassen Objekte sind, die Instanzen erzeugen können.
Nun, eigentlich sind Klassen selbst Instanzen. Von Metaklassen.
>>> class Foo(object): pass
>>> id(Foo)
142630324
In Python ist alles ein Objekt, und alle sind entweder Instanzen von Klassen oder Instanzen von Metaklassen.
Außer bei type
.
type
ist eigentlich eine eigene Metaklasse. Dies ist nicht etwas, das Sie in reinem Python reproduzieren und wird durch ein wenig Schummeln auf der Implementierungsebene Ebene.
Zweitens: Metaklassen sind kompliziert. Sie wollen sie vielleicht nicht für sehr einfache Klassenänderungen verwenden. Sie können Klassen mithilfe von zwei verschiedenen Techniken ändern:
In 99 % der Fälle, in denen Sie eine Klassenänderung benötigen, sind Sie mit diesen besser dran.
Aber in 98 % der Fälle brauchen Sie überhaupt keine Klassenänderung.