Ich habe einen Freund, der gerne Metaklassen verwendet und diese regelmäßig als Lösung anbietet.
Ich bin der Meinung, dass Sie fast nie Metaklassen verwenden müssen. Warum? weil ich denke, wenn Sie so etwas mit einer Klasse machen, sollten Sie es wahrscheinlich mit einem Objekt machen. Und ein kleines Redesign / Refactor ist in Ordnung.
Die Möglichkeit, Metaklassen zu verwenden, hat dazu geführt, dass viele Menschen an vielen Orten Klassen als eine Art zweitklassiges Objekt verwenden, was mir nur katastrophal erscheint. Soll die Programmierung durch Metaprogrammierung ersetzt werden? Die Hinzufügung von Klassendekorateuren hat es leider noch akzeptabler gemacht.
Ich bin verzweifelt, Ihre gültigen (konkreten) Anwendungsfälle für Metaklassen in Python zu kennen. Oder um zu erfahren, warum das Mutieren von Klassen manchmal besser ist als das Mutieren von Objekten.
Ich werde beginnen:
Manchmal ist es bei Verwendung einer Bibliothek eines Drittanbieters hilfreich, die Klasse auf eine bestimmte Weise mutieren zu können.
(Dies ist der einzige Fall, an den ich denken kann, und er ist nicht konkret.)
Antworten:
Ich habe eine Klasse, die nicht interaktives Plotten als Frontend für Matplotlib behandelt. Gelegentlich möchte man jedoch interaktiv zeichnen. Mit nur ein paar Funktionen stellte ich fest, dass ich die Anzahl der Zahlen erhöhen, die Zeichnung manuell aufrufen usw. konnte, aber ich musste dies vor und nach jedem Plotaufruf tun. Um sowohl einen interaktiven Plot-Wrapper als auch einen Offscreen-Plot-Wrapper zu erstellen, fand ich es effizienter, dies über Metaklassen zu tun und die entsprechenden Methoden zu verpacken, als Folgendes zu tun:
Diese Methode hält sich nicht mit API-Änderungen usw. auf
__init__
dem Laufenden , aber eine Methode, die die Klassenattribute vor dem erneuten Festlegen der Klassenattribute durchläuft, ist effizienter und hält die Dinge auf dem neuesten Stand:Natürlich gibt es vielleicht bessere Möglichkeiten, dies zu tun, aber ich habe festgestellt, dass dies effektiv ist. Natürlich könnte dies auch in
__new__
oder geschehen__init__
, aber dies war die Lösung, die ich am einfachsten fand.quelle
Vor kurzem wurde mir dieselbe Frage gestellt, und ich fand mehrere Antworten. Ich hoffe, es ist in Ordnung, diesen Thread wiederzubeleben, da ich einige der genannten Anwendungsfälle näher erläutern und einige neue hinzufügen wollte.
Die meisten Metaklassen, die ich gesehen habe, machen eines von zwei Dingen:
Registrierung (Hinzufügen einer Klasse zu einer Datenstruktur):
Wann immer Sie eine Unterklasse haben
Model
, wird Ihre Klasse immodels
Wörterbuch registriert :Dies kann auch mit Klassendekorateuren durchgeführt werden:
Oder mit einer expliziten Registrierungsfunktion:
Eigentlich ist das so ziemlich das Gleiche: Sie erwähnen Klassendekorateure ungünstig, aber es ist wirklich nichts anderes als syntaktischer Zucker für einen Funktionsaufruf für eine Klasse, also gibt es keine Magie.
Der Vorteil von Metaklassen ist in diesem Fall die Vererbung, da sie für alle Unterklassen funktionieren, während die anderen Lösungen nur für Unterklassen funktionieren, die explizit dekoriert oder registriert sind.
Refactoring (Ändern von Klassenattributen oder Hinzufügen neuer Attribute):
Wenn Sie
Model
einigeField
Attribute unterordnen und definieren , werden ihnen ihre Namen hinzugefügt (z. B. für informativere Fehlermeldungen) und in einem_fields
Wörterbuch zusammengefasst (für eine einfache Iteration, ohne dass alle Klassenattribute und alle ihre Basisklassen durchsucht werden müssen.) Attribute jedes Mal):Auch dies kann (ohne Vererbung) mit einem Klassendekorateur durchgeführt werden:
Oder explizit:
Obwohl dies im Gegensatz zu Ihrer Befürwortung einer lesbaren und wartbaren Nicht-Meta-Programmierung viel umständlicher, redundanter und fehleranfälliger ist:
Nachdem Sie die häufigsten und konkretesten Anwendungsfälle berücksichtigt haben, müssen Sie nur dann unbedingt Metaklassen verwenden, wenn Sie den Klassennamen oder die Liste der Basisklassen ändern möchten, da diese Parameter nach ihrer Definition in die Klasse eingebrannt werden und kein Dekorator vorhanden ist oder Funktion kann sie aufbacken.
Dies kann in Frameworks nützlich sein, um Warnungen auszugeben, wenn Klassen mit ähnlichen Namen oder unvollständigen Vererbungsbäumen definiert sind, aber ich kann mir keinen Grund neben dem Trolling vorstellen, diese Werte tatsächlich zu ändern. Vielleicht kann David Beazley.
Wie auch immer, in Python 3 haben Metaklassen auch die
__prepare__
Methode, mit der Sie den Klassenkörper in einer anderen Zuordnung als a auswerten könnendict
, um geordnete Attribute, überladene Attribute und andere böse coole Dinge zu unterstützen:Sie könnten argumentieren, dass geordnete Attribute mit Erstellungszählern erreicht werden können und das Überladen mit Standardargumenten simuliert werden kann:
Es ist nicht nur viel hässlicher, sondern auch weniger flexibel: Was ist, wenn Sie geordnete Literalattribute wie Ganzzahlen und Zeichenfolgen möchten? Was ist, wenn
None
ein gültiger Wert für istx
?Hier ist ein kreativer Weg, um das erste Problem zu lösen:
Und hier ist ein kreativer Weg, um den zweiten zu lösen:
Aber das ist viel, VIEL Voodoo-er als eine einfache Metaklasse (besonders die erste, die Ihr Gehirn wirklich zum Schmelzen bringt). Mein Punkt ist, dass Sie Metaklassen als ungewohnt und kontraintuitiv betrachten, aber Sie können sie auch als nächsten Schritt der Evolution in Programmiersprachen betrachten: Sie müssen nur Ihre Denkweise anpassen. Schließlich könnten Sie wahrscheinlich alles in C tun, einschließlich der Definition einer Struktur mit Funktionszeigern und der Übergabe als erstes Argument an ihre Funktionen. Eine Person, die C ++ zum ersten Mal sieht, könnte sagen: "Was ist diese Magie? Warum übergibt der Compiler implizit
this
zu Methoden, aber nicht zu regulären und statischen Funktionen? Es ist besser, explizit und ausführlich über Ihre Argumente zu sprechen. "Aber dann ist objektorientierte Programmierung viel leistungsfähiger, wenn Sie sie erhalten, und dies ist auch ... ähm ... quasi-aspektorientierte Programmierung, denke ich. Und wenn Sie es einmal tun Metaklassen verstehen, sie sind eigentlich sehr einfach. Warum also nicht verwenden, wenn es Ihnen passt?Und schließlich sind Metaklassen radikal und das Programmieren sollte Spaß machen. Die ständige Verwendung von Standardprogrammierkonstrukten und Entwurfsmustern ist langweilig und wenig inspirierend und behindert Ihre Vorstellungskraft. Lebe ein bisschen! Hier ist eine Metametaklasse, nur für dich.
Bearbeiten
Dies ist eine ziemlich alte Frage, aber sie wird immer noch positiv bewertet, daher dachte ich, ich würde einen Link zu einer umfassenderen Antwort hinzufügen. Wenn Sie möchten mehr über metaclasses und ihre Verwendung lesen, ich habe gerade einen Artikel darüber veröffentlicht hier .
quelle
__metaclass__
Attribut erbt , aber dieses Attribut ist in Python 3 nicht mehr speziell. Gibt es eine Möglichkeit, dieses "Kinderklassen werden auch von der Metaklasse der Eltern erstellt" in Python 3 zum Laufen zu bringen ?init_subclass
Allerdings hat Python 3.6 das viel einfachere eingeführt , sodass Sie jetzt Unterklassen in einer Basisklasse bearbeiten können und für diesen Zweck keine Metaklasse mehr benötigen.Der Zweck von Metaklassen besteht nicht darin, die Unterscheidung zwischen Klasse und Objekt durch Metaklasse / Klasse zu ersetzen, sondern das Verhalten von Klassendefinitionen (und damit deren Instanzen) auf irgendeine Weise zu ändern. Tatsächlich wird das Verhalten der Klassenanweisung so geändert, dass es für Ihre bestimmte Domäne möglicherweise nützlicher ist als die Standardeinstellung. Die Dinge, für die ich sie verwendet habe, sind:
Verfolgen von Unterklassen, normalerweise zum Registrieren von Handlern. Dies ist praktisch, wenn Sie ein Plugin-Setup verwenden, bei dem Sie einen Handler für eine bestimmte Sache registrieren möchten, indem Sie einfach einige Klassenattribute unterordnen und einrichten. z.B. Angenommen, Sie schreiben einen Handler für verschiedene Musikformate, wobei jede Klasse geeignete Methoden (Play / Get-Tags usw.) für ihren Typ implementiert. Das Hinzufügen eines Handlers für einen neuen Typ wird zu:
Die Metaklasse verwaltet dann ein Wörterbuch von
{'.mp3' : MP3File, ... }
usw. und erstellt ein Objekt des entsprechenden Typs, wenn Sie einen Handler über eine Factory-Funktion anfordern.Verhalten ändern. Möglicherweise möchten Sie bestimmten Attributen eine besondere Bedeutung beimessen, was zu einem veränderten Verhalten führt, wenn sie vorhanden sind. Zum Beispiel können Sie für Methoden mit dem Namen aussehen wollen
_get_foo
und_set_foo
und transparent wandeln sie in Eigenschaften. Als Beispiel aus der Praxis habe ich hier ein Rezept geschrieben, um mehr C-ähnliche Strukturdefinitionen zu erhalten. Die Metaklasse wird verwendet, um die deklarierten Elemente in eine Zeichenfolge im Strukturformat zu konvertieren, die Vererbung usw. zu behandeln und eine Klasse zu erstellen, die damit umgehen kann.Weitere Beispiele aus der Praxis finden Sie in verschiedenen ORMs wie dem ORM oder dem sqlobject von sqlalchemy . Auch hier besteht der Zweck darin, Definitionen (hier SQL-Spaltendefinitionen) mit einer bestimmten Bedeutung zu interpretieren.
quelle
Beginnen wir mit Tim Peters klassischem Zitat:
Trotzdem bin ich (regelmäßig) auf echte Verwendungen von Metaklassen gestoßen. Das, was mir in den Sinn kommt, ist in Django, wo alle Ihre Modelle von models.Model erben. models.Model wiederum macht ernsthafte Magie, um Ihre DB-Modelle mit Djangos ORM-Güte zu versehen. Diese Magie geschieht über Metaklassen. Es werden alle Arten von Ausnahmeklassen, Managerklassen usw. usw. erstellt.
Siehe django / db / models / base.py, Klasse ModelBase () für den Anfang der Geschichte.
quelle
Metaklassen können nützlich sein, um domänenspezifische Sprachen in Python zu erstellen. Konkrete Beispiele sind Django, die deklarative Syntax von SQLObject für Datenbankschemata.
Ein grundlegendes Beispiel aus einer konservativen Metaklasse von Ian Bicking:
Einige andere Techniken: Zutaten zum Erstellen einer DSL in Python (pdf).
Bearbeiten (von Ali): Ein Beispiel dafür, wie Sammlungen und Instanzen verwendet werden, würde ich bevorzugen. Die wichtige Tatsache sind die Instanzen, die Ihnen mehr Leistung geben und den Grund für die Verwendung von Metaklassen beseitigen. Erwähnenswert ist auch, dass Ihr Beispiel eine Mischung aus Klassen und Instanzen verwendet, was sicherlich ein Hinweis darauf ist, dass Sie nicht alles mit Metaklassen tun können. Und schafft eine wirklich uneinheitliche Art und Weise, dies zu tun.
Es ist nicht perfekt, aber es gibt bereits fast keine Magie, keine Notwendigkeit für Metaklassen und eine verbesserte Gleichmäßigkeit.
quelle
Ein vernünftiges Muster für die Verwendung von Metaklassen besteht darin, etwas einmal zu tun, wenn eine Klasse definiert wird, und nicht wiederholt, wenn dieselbe Klasse instanziiert wird.
Wenn mehrere Klassen dasselbe spezielle Verhalten aufweisen, ist das Wiederholen
__metaclass__=X
offensichtlich besser als das Wiederholen des speziellen Codes und / oder das Einführen von gemeinsam genutzten Ad-hoc-Superklassen.Aber selbst mit nur einer speziellen Klasse und ohne vorhersehbare Erweiterung
__new__
und__init__
einer Metaklasse können Klassenvariablen oder andere globale Daten sauberer initialisiert werden, als Spezialcode und Normalendef
undclass
Anweisungen im Klassendefinitionskörper zu vermischen .quelle
Ich habe in Python nur Metaklassen verwendet, als ich einen Wrapper für die Flickr-API geschrieben habe.
Mein Ziel war es, die API-Site von flickr zu durchsuchen und dynamisch eine vollständige Klassenhierarchie zu generieren, um den API-Zugriff mithilfe von Python-Objekten zu ermöglichen:
In diesem Beispiel kenne ich die Klassendefinitionen zur Laufzeit nicht, da ich die gesamte Python Flickr-API von der Website generiert habe. Es war sehr nützlich, Typen dynamisch generieren zu können.
quelle
Ich habe erst gestern dasselbe gedacht und stimme vollkommen zu. Die Komplikationen im Code, die durch Versuche verursacht werden, ihn deklarativer zu gestalten, machen die Codebasis meiner Meinung nach im Allgemeinen schwieriger zu pflegen, schwerer zu lesen und weniger pythonisch. Normalerweise erfordert es auch viel copy.copy () ing (um die Vererbung aufrechtzuerhalten und von der Klasse in die Instanz zu kopieren) und bedeutet, dass Sie an vielen Stellen nachsehen müssen, was los ist (immer von der Metaklasse aufwärts), was gegen die Python Korn auch. Ich habe Formencode und SQLalchemy-Code durchgesehen, um zu sehen, ob sich ein solcher deklarativer Stil gelohnt hat und es eindeutig nicht. Ein solcher Stil sollte Deskriptoren (wie Eigenschaften und Methoden) und unveränderlichen Daten überlassen bleiben. Ruby hat eine bessere Unterstützung für solche deklarativen Stile und ich bin froh, dass die Kernsprache von Python diesen Weg nicht einschlägt.
Ich kann ihre Verwendung für das Debuggen sehen und allen Ihren Basisklassen eine Metaklasse hinzufügen, um umfassendere Informationen zu erhalten. Ich sehe ihre Verwendung auch nur in (sehr) großen Projekten, um etwas Boilerplate-Code loszuwerden (aber bei Verlust der Klarheit). sqlalchemy zum Beispiel hat sie an anderer Stelle verwendet werden , eine bestimmte benutzerdefinierte Methode für alle auf einem Attributwert in der Klassendefinition zB ein Spielzeug Beispiel basierend Subklassen hinzufügen
könnte eine Metaklasse haben, die eine Methode in dieser Klasse mit speziellen Eigenschaften basierend auf "Hallo" generiert (sagen wir eine Methode, die "Hallo" am Ende einer Zeichenfolge hinzufügt). Für die Wartbarkeit kann es hilfreich sein, sicherzustellen, dass Sie nicht in jeder von Ihnen erstellten Unterklasse eine Methode schreiben müssen, sondern nur method_maker_value definieren müssen.
Die Notwendigkeit dafür ist jedoch so selten und reduziert nur ein wenig Tipparbeit, dass es sich nicht wirklich lohnt, darüber nachzudenken, es sei denn, Sie haben eine ausreichend große Codebasis.
quelle
Man kann nie absolut brauchen eine Metaklasse zu verwenden, da man immer eine Klasse konstruieren kann , das tut , was Sie Vererbung oder Aggregation der Klasse wollen verwenden Sie ändern möchten.
Trotzdem kann es in Smalltalk und Ruby sehr praktisch sein, eine vorhandene Klasse zu ändern, aber Python macht das nicht gerne direkt.
Es gibt einen ausgezeichneten DeveloperWorks-Artikel zum Thema Metaclassing in Python, der möglicherweise hilfreich ist. Der Wikipedia-Artikel ist auch ziemlich gut.
quelle
Einige GUI-Bibliotheken haben Probleme, wenn mehrere Threads versuchen, mit ihnen zu interagieren.
tkinter
ist ein solches Beispiel; und während man das Problem explizit mit Ereignissen und Warteschlangen behandeln kann, kann es viel einfacher sein, die Bibliothek auf eine Weise zu verwenden, die das Problem insgesamt ignoriert. Siehe - die Magie der Metaklassen.Unter bestimmten Umständen kann es äußerst hilfreich sein, eine gesamte Bibliothek nahtlos dynamisch neu zu schreiben, damit sie wie erwartet in einer Multithread-Anwendung ordnungsgemäß funktioniert. Das safetkinter- Modul erledigt dies mithilfe einer vom Threadbox- Modul bereitgestellten Metaklasse - Ereignisse und Warteschlangen werden nicht benötigt.
Ein netter Aspekt
threadbox
ist, dass es egal ist, welche Klasse es klont. Es enthält ein Beispiel dafür, wie alle Basisklassen bei Bedarf von einer Metaklasse berührt werden können. Ein weiterer Vorteil von Metaklassen besteht darin, dass sie auch beim Erben von Klassen ausgeführt werden. Programme, die selbst schreiben - warum nicht?quelle
Der einzige legitime Anwendungsfall einer Metaklasse besteht darin, andere neugierige Entwickler davon abzuhalten, Ihren Code zu berühren. Sobald ein neugieriger Entwickler Metaklassen beherrscht und anfängt, mit Ihren herumzustöbern, werfen Sie ein oder zwei weitere Level ein, um sie fernzuhalten. Wenn dies nicht funktioniert,
type.__new__
verwenden Sie eine rekursive Metaklasse oder ein Schema.(geschriebene Zunge in die Wange, aber ich habe diese Art der Verschleierung gesehen. Django ist ein perfektes Beispiel)
quelle
Metaklassen ersetzen nicht die Programmierung! Sie sind nur ein Trick, mit dem einige Aufgaben automatisiert oder eleganter gestaltet werden können. Ein gutes Beispiel hierfür ist die Bibliothek zur Hervorhebung der Pylements- Syntax. Es hat eine Klasse namens
RegexLexer
der der Benutzer eine Reihe von Lexierungsregeln als reguläre Ausdrücke für eine Klasse definieren kann. Eine Metaklasse wird verwendet, um die Definitionen in einen nützlichen Parser umzuwandeln.Sie sind wie Salz; es ist einfach zu viel zu verwenden.
quelle
Ich habe Metaklassen verwendet, um Klassen einige Attribute bereitzustellen. Nehmen Sie zum Beispiel:
legt das wird name - Attribut für jede Klasse, die den Metaklasse Satz muß NameClass zeigen.
quelle
Dies ist eine geringfügige Verwendung, aber ... eine Sache, für die ich Metaklassen nützlich fand, ist das Aufrufen einer Funktion, wenn eine Unterklasse erstellt wird. Ich habe dies in eine Metaklasse kodifiziert, die nach einem
__initsubclass__
Attribut sucht : Immer wenn eine Unterklasse erstellt wird, werden alle übergeordneten Klassen aufgerufen, die diese Methode definieren__initsubclass__(cls, subcls)
. Dies ermöglicht die Erstellung einer übergeordneten Klasse, die dann alle Unterklassen bei einer globalen Registrierung registriert, bei jeder Definition invariante Überprüfungen für Unterklassen durchführt, späte Bindungsvorgänge ausführt usw., ohne dass Funktionen manuell aufgerufen oder benutzerdefinierte Metaklassen erstellt werden müssen Führen Sie jede dieser separaten Aufgaben aus.Wohlgemerkt, ich habe langsam erkannt, dass die implizite Magie dieses Verhaltens etwas unerwünscht ist, da es unerwartet ist, wenn man eine Klassendefinition außerhalb des Kontexts betrachtet ... und deshalb habe ich mich davon entfernt, diese Lösung für irgendetwas Ernstes zu verwenden Initialisieren eines
__super
Attributs für jede Klasse und Instanz.quelle
Ich musste kürzlich eine Metaklasse verwenden, um ein SQLAlchemy-Modell deklarativ um eine Datenbanktabelle zu definieren, die mit US-Volkszählungsdaten von http://census.ire.org/data/bulkdata.html gefüllt ist
IRE stellt Datenbank-Shells für die Volkszählungsdatentabellen bereit , die ganzzahlige Spalten gemäß einer Namenskonvention des Census Bureau von p012015, p012016, p012017 usw. erstellen.
Ich wollte a) über eine
model_instance.p012017
Syntax auf diese Spalten zugreifen können , b) ziemlich explizit angeben, was ich tat, und c) nicht explizit Dutzende von Feldern im Modell definieren müssen, also habe ich SQLAlchemy'sDeclarativeMeta
in Unterklassen unterteilt , um eine Reihe von zu durchlaufen die Spalten und erstellen automatisch Modellfelder, die den Spalten entsprechen:Ich könnte diese Metaklasse dann für meine Modelldefinition verwenden und auf die automatisch aufgezählten Felder des Modells zugreifen:
quelle
Es scheint eine legitime Verwendung zu geben, die hier beschrieben wird - das Umschreiben von Python-Dokumentzeichenfolgen mit einer Metaklasse.
quelle
Ich musste sie einmal für einen binären Parser verwenden, um die Verwendung zu vereinfachen. Sie definieren eine Nachrichtenklasse mit Attributen der Felder, die auf der Leitung vorhanden sind. Sie mussten so bestellt werden, wie sie deklariert wurden, um das endgültige Drahtformat daraus zu erstellen. Sie können dies mit Metaklassen tun, wenn Sie ein geordnetes Namespace-Diktat verwenden. In der Tat ist es in den Beispielen für Metaklassen:
https://docs.python.org/3/reference/datamodel.html#metaclass-example
Aber im Allgemeinen: Bewerten Sie sehr sorgfältig, ob Sie die zusätzliche Komplexität von Metaklassen wirklich wirklich benötigen.
quelle
Die Antwort von @Dan Gittik ist cool
Die Beispiele am Ende könnten viele Dinge verdeutlichen. Ich habe es in Python 3 geändert und einige Erklärungen gegeben:
quelle
Ein weiterer Anwendungsfall ist, wenn Sie Attribute auf Klassenebene ändern möchten und sicherstellen möchten, dass sie nur das jeweilige Objekt betreffen. In der Praxis bedeutet dies, dass die Phasen von Metaklassen und Klasseninstanziierungen "zusammengeführt" werden, sodass Sie sich nur mit Klasseninstanzen ihrer eigenen (eindeutigen) Art befassen müssen.
Ich musste das auch tun, wenn wir (aus Gründen der Lesbarkeit und des Polymorphismus ) dynamisch
property
s definieren wollten, welche zurückgegebenen Werte (möglicherweise) aus Berechnungen resultieren, die auf (häufig sich ändernden) Attributen auf Instanzebene basieren, was nur auf Klassenebene möglich ist , dh nach der metaclass Instanziierung und vor der Klasse Instanziierung.quelle