Was sind einige (konkrete) Anwendungsfälle für Metaklassen?

117

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.)

Ali Afshar
quelle
3
Das ist eine gute Frage. Den folgenden Antworten nach zu urteilen, ist es ziemlich klar, dass es keine konkrete Verwendung für Metaklassen gibt.
Marcus Ottosson

Antworten:

25

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:

class PlottingInteractive:
    add_slice = wrap_pylab_newplot(add_slice)

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:

class _Interactify(type):
    def __init__(cls, name, bases, d):
        super(_Interactify, cls).__init__(name, bases, d)
        for base in bases:
            for attrname in dir(base):
                if attrname in d: continue # If overridden, don't reset
                attr = getattr(cls, attrname)
                if type(attr) == types.MethodType:
                    if attrname.startswith("add_"):
                        setattr(cls, attrname, wrap_pylab_newplot(attr))
                    elif attrname.startswith("set_"):
                        setattr(cls, attrname, wrap_pylab_show(attr))

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.

Matt
quelle
103

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:

  1. Registrierung (Hinzufügen einer Klasse zu einer Datenstruktur):

    models = {}
    
    class ModelMetaclass(type):
        def __new__(meta, name, bases, attrs):
            models[name] = cls = type.__new__(meta, name, bases, attrs)
            return cls
    
    class Model(object):
        __metaclass__ = ModelMetaclass

    Wann immer Sie eine Unterklasse haben Model, wird Ihre Klasse im modelsWörterbuch registriert :

    >>> class A(Model):
    ...     pass
    ...
    >>> class B(A):
    ...     pass
    ...
    >>> models
    {'A': <__main__.A class at 0x...>,
     'B': <__main__.B class at 0x...>}

    Dies kann auch mit Klassendekorateuren durchgeführt werden:

    models = {}
    
    def model(cls):
        models[cls.__name__] = cls
        return cls
    
    @model
    class A(object):
        pass

    Oder mit einer expliziten Registrierungsfunktion:

    models = {}
    
    def register_model(cls):
        models[cls.__name__] = cls
    
    class A(object):
        pass
    
    register_model(A)

    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.

    >>> class B(A):
    ...     pass
    ...
    >>> models
    {'A': <__main__.A class at 0x...> # No B :(
  2. Refactoring (Ändern von Klassenattributen oder Hinzufügen neuer Attribute):

    class ModelMetaclass(type):
        def __new__(meta, name, bases, attrs):
            fields = {}
            for key, value in attrs.items():
                if isinstance(value, Field):
                    value.name = '%s.%s' % (name, key)
                    fields[key] = value
            for base in bases:
                if hasattr(base, '_fields'):
                    fields.update(base._fields)
            attrs['_fields'] = fields
            return type.__new__(meta, name, bases, attrs)
    
    class Model(object):
        __metaclass__ = ModelMetaclass

    Wenn Sie Modeleinige FieldAttribute unterordnen und definieren , werden ihnen ihre Namen hinzugefügt (z. B. für informativere Fehlermeldungen) und in einem _fieldsWörterbuch zusammengefasst (für eine einfache Iteration, ohne dass alle Klassenattribute und alle ihre Basisklassen durchsucht werden müssen.) Attribute jedes Mal):

    >>> class A(Model):
    ...     foo = Integer()
    ...
    >>> class B(A):
    ...     bar = String()
    ...
    >>> B._fields
    {'foo': Integer('A.foo'), 'bar': String('B.bar')}

    Auch dies kann (ohne Vererbung) mit einem Klassendekorateur durchgeführt werden:

    def model(cls):
        fields = {}
        for key, value in vars(cls).items():
            if isinstance(value, Field):
                value.name = '%s.%s' % (cls.__name__, key)
                fields[key] = value
        for base in cls.__bases__:
            if hasattr(base, '_fields'):
                fields.update(base._fields)
        cls._fields = fields
        return cls
    
    @model
    class A(object):
        foo = Integer()
    
    class B(A):
        bar = String()
    
    # B.bar has no name :(
    # B._fields is {'foo': Integer('A.foo')} :(

    Oder explizit:

    class A(object):
        foo = Integer('A.foo')
        _fields = {'foo': foo} # Don't forget all the base classes' fields, too!

    Obwohl dies im Gegensatz zu Ihrer Befürwortung einer lesbaren und wartbaren Nicht-Meta-Programmierung viel umständlicher, redundanter und fehleranfälliger ist:

    class B(A):
        bar = String()
    
    # vs.
    
    class B(A):
        bar = String('bar')
        _fields = {'B.bar': bar, 'A.foo': A.foo}

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.

class Metaclass(type):
    def __new__(meta, name, bases, attrs):
        return type.__new__(meta, 'foo', (int,), attrs)

class Baseclass(object):
    __metaclass__ = Metaclass

class A(Baseclass):
    pass

class B(A):
    pass

print A.__name__ # foo
print B.__name__ # foo
print issubclass(B, A)   # False
print issubclass(B, int) # True

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önnen dict, um geordnete Attribute, überladene Attribute und andere böse coole Dinge zu unterstützen:

import collections

class Metaclass(type):

    @classmethod
    def __prepare__(meta, name, bases, **kwds):
        return collections.OrderedDict()

    def __new__(meta, name, bases, attrs, **kwds):
        print(list(attrs))
        # Do more stuff...

class A(metaclass=Metaclass):
    x = 1
    y = 2

# prints ['x', 'y'] rather than ['y', 'x']

 

class ListDict(dict):
    def __setitem__(self, key, value):
        self.setdefault(key, []).append(value)

class Metaclass(type):

    @classmethod
    def __prepare__(meta, name, bases, **kwds):
        return ListDict()

    def __new__(meta, name, bases, attrs, **kwds):
        print(attrs['foo'])
        # Do more stuff...

class A(metaclass=Metaclass):

    def foo(self):
        pass

    def foo(self, x):
        pass

# prints [<function foo at 0x...>, <function foo at 0x...>] rather than <function foo at 0x...>

Sie könnten argumentieren, dass geordnete Attribute mit Erstellungszählern erreicht werden können und das Überladen mit Standardargumenten simuliert werden kann:

import itertools

class Attribute(object):
    _counter = itertools.count()
    def __init__(self):
        self._count = Attribute._counter.next()

class A(object):
    x = Attribute()
    y = Attribute()

A._order = sorted([(k, v) for k, v in vars(A).items() if isinstance(v, Attribute)],
                  key = lambda (k, v): v._count)

 

class A(object):

    def _foo0(self):
        pass

    def _foo1(self, x):
        pass

    def foo(self, x=None):
        if x is None:
            return self._foo0()
        else:
            return self._foo1(x)

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 Noneein gültiger Wert für ist x?

Hier ist ein kreativer Weg, um das erste Problem zu lösen:

import sys

class Builder(object):
    def __call__(self, cls):
        cls._order = self.frame.f_code.co_names
        return cls

def ordered():
    builder = Builder()
    def trace(frame, event, arg):
        builder.frame = frame
        sys.settrace(None)
    sys.settrace(trace)
    return builder

@ordered()
class A(object):
    x = 1
    y = 'foo'

print A._order # ['x', 'y']

Und hier ist ein kreativer Weg, um den zweiten zu lösen:

_undefined = object()

class A(object):

    def _foo0(self):
        pass

    def _foo1(self, x):
        pass

    def foo(self, x=_undefined):
        if x is _undefined:
            return self._foo0()
        else:
            return self._foo1(x)

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 implizitthiszu 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.

class MetaMetaclass(type):
    def __new__(meta, name, bases, attrs):
        def __new__(meta, name, bases, attrs):
            cls = type.__new__(meta, name, bases, attrs)
            cls._label = 'Made in %s' % meta.__name__
            return cls 
        attrs['__new__'] = __new__
        return type.__new__(meta, name, bases, attrs)

class China(type):
    __metaclass__ = MetaMetaclass

class Taiwan(type):
    __metaclass__ = MetaMetaclass

class A(object):
    __metaclass__ = China

class B(object):
    __metaclass__ = Taiwan

print A._label # Made in China
print B._label # Made in Taiwan

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 .

Dan Gittik
quelle
5
Das ist eine großartige Antwort, danke für die Zeit, die Sie geschrieben und mehrere Beispiele gegeben haben
Chen A.
"... der Vorteil von Metaklassen in diesem Fall ist die Vererbung, da sie für alle Unterklassen funktionieren" - vermutlich nicht in Python 3? Ich denke, es funktioniert in Python 2 nur, weil jede untergeordnete Klasse das __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 ?
ForceBru
2
Dies gilt auch für Python 3, da eine von B ererbte Klasse B, deren Metaklasse M ist, ebenfalls ein Typ von M ist. Wenn also B ausgewertet wird, wird M aufgerufen, um es zu erstellen, und dies ermöglicht es Ihnen effektiv "an Unterklassen arbeiten" (von A). init_subclassAllerdings 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.
Dan Gittik
Das ist großartig, ich habe so viele Blog-Beiträge über Metaklassen gelesen, nur dieser macht die Vor- und Nachteile und Alternativen zur Metaklasse bekannt.
ospider
36

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:

    class Mp3File(MusicFile):
        extensions = ['.mp3']  # Register this type as a handler for mp3 files
        ...
        # Implementation of mp3 methods go here

    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_foound _set_foound 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.

Brian
quelle
3
Nun ja, Unterklassen verfolgen. Aber warum willst du das jemals? Ihr Beispiel ist nur implizit für register_music_file (Mp3File, ['.mp3']) und der explizite Weg ist besser lesbar und wartbar. Dies ist ein Beispiel für die schlimmen Fälle, über die ich spreche.
Ali Afshar
Sprechen Sie über den ORM-Fall über die klassenbasierte Art der Definition von Tabellen oder über die Metaklassen für zugeordnete Objekte. Weil SQLAlchemy (zu Recht) jeder Klasse zugeordnet werden kann (und ich gehe davon aus, dass für diese Aktivität keine Metaklasse verwendet wird).
Ali Afshar
10
Ich bevorzuge den deklarativeren Stil, anstatt zusätzliche Registrierungsmethoden für jede Unterklasse zu erfordern - besser, wenn alles an einem einzigen Ort verpackt ist.
Brian
Bei der SQLalchemie denke ich hauptsächlich an die deklarative Ebene. Vielleicht ist das SQL-Objekt ein besseres Beispiel. Die intern verwendeten Metaklassen sind jedoch auch Beispiele für eine ähnliche Neuinterpretation bestimmter Attribute, um die Bedeutung zu deklarieren.
Brian
2
Tut mir leid, dass eine meiner Aussagen im SO-Timeout-Szenario verloren gegangen ist. Ich finde Klassen für deklarative fast ein Greuel. Ich weiß, dass die Leute es lieben, und es ist akzeptiertes Verhalten. Aber (aus Erfahrung) ich weiß, dass es in einer Situation unbrauchbar ist, in der Sie Dinge von der UN deklarieren wollen. Eine Klasse abzumelden ist schwierig .
Ali Afshar
17

Beginnen wir mit Tim Peters klassischem Zitat:

Metaklassen sind eine tiefere Magie, als 99% der Benutzer sich jemals Sorgen machen sollten. Wenn Sie sich fragen, ob Sie sie brauchen, tun Sie das nicht (die Leute, die sie tatsächlich brauchen, wissen mit Sicherheit, dass sie sie brauchen, und brauchen keine Erklärung, warum). Tim Peters (clp post 2002-12-22)

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.

Peter Rowell
quelle
Nun ja, ich verstehe den Punkt. Ich frage mich nicht, wie oder warum Metaklassen verwendet werden sollen, ich frage mich, wer und was. ORMs sind hier ein häufiger Fall, wie ich sehe. Leider ist Djangos ORM im Vergleich zu SQLAlchemy mit weniger Magie ziemlich schlecht. Magie ist schlecht und Metaklassen sind dafür wirklich nicht notwendig.
Ali Afshar
9
Nachdem Tim Peters 'Zitat in der Vergangenheit gelesen hat, hat die Zeit gezeigt, dass seine Aussage eher wenig hilfreich ist. Erst als Python-Metaklassen hier auf StackOverflow untersucht wurden, wurde klar, wie sie überhaupt implementiert werden können. Nachdem ich mich gezwungen hatte, das Schreiben und Verwenden von Metaklassen zu lernen, überraschten mich ihre Fähigkeiten und gaben mir ein viel besseres Verständnis dafür, wie Python wirklich funktioniert. Klassen können wiederverwendbaren Code bereitstellen, und Metaklassen können wiederverwendbare Verbesserungen für diese Klassen bereitstellen.
Noctis Skytower
6

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:

Die Metaklassen, die ich verwendet habe, wurden hauptsächlich verwendet, um eine Art deklarativen Programmierstil zu unterstützen. Betrachten Sie beispielsweise ein Validierungsschema:

class Registration(schema.Schema):
    first_name = validators.String(notEmpty=True)
    last_name = validators.String(notEmpty=True)
    mi = validators.MaxLength(1)
    class Numbers(foreach.ForEach):
        class Number(schema.Schema):
            type = validators.OneOf(['home', 'work'])
            phone_number = validators.PhoneNumber()

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.

number_validator = [
    v.OneOf('type', ['home', 'work']),
    v.PhoneNumber('phone_number'),
]

validators = [
    v.String('first_name', notEmpty=True),
    v.String('last_name', notEmpty=True),
    v.MaxLength('mi', 1),
    v.ForEach([number_validator,])
]

Es ist nicht perfekt, aber es gibt bereits fast keine Magie, keine Notwendigkeit für Metaklassen und eine verbesserte Gleichmäßigkeit.

jfs
quelle
Danke dafür. Dies ist ein sehr gutes Beispiel für einen Anwendungsfall, der meiner Meinung nach unnötig, hässlich und nicht verwaltbar ist und auf der Grundlage einer einfachen Sammlungsinstanz (mit verschachtelten Sammlungen nach Bedarf) einfacher wäre.
Ali Afshar
1
@Ali A: Sie können gerne ein konkretes Beispiel für den direkten Vergleich zwischen deklarativer Syntax über Metaklassen und einem Ansatz auf der Grundlage einer einfachen Erfassungsinstanz liefern.
JFS
@Ali A: Sie können meine Antwort direkt bearbeiten, um ein Beispiel für einen Sammlungsstil hinzuzufügen.
JFS
Ok, das gemacht. Entschuldigung, ich habe es heute etwas eilig, werde aber versuchen, alle Fragen später / morgen zu beantworten. Schöne Ferien!
Ali Afshar
2
Das zweite Beispiel ist hässlich, da Sie die Validator-Instanz mit ihrem Namen verknüpfen mussten. Eine etwas bessere Möglichkeit besteht darin, ein Wörterbuch anstelle einer Liste zu verwenden. In Python-Klassen handelt es sich jedoch nur um Syntaxzucker für Wörterbücher. Warum also nicht Klassen verwenden? Sie erhalten auch eine kostenlose Namensüberprüfung, da Python-Babes keine Leerzeichen oder Sonderzeichen enthalten können, die eine Zeichenfolge enthalten könnte.
Lie Ryan
6

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__=Xoffensichtlich 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 Normalen defund classAnweisungen im Klassendefinitionskörper zu vermischen .

user412090
quelle
5

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:

# Both the photo type and the flickr.photos.search API method 
# are generated at "run-time"
for photo in flickr.photos.search(text=balloons):
    print photo.description

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.

Triptychon
quelle
2
Sie können Typen dynamisch generieren, ohne Metaklassen zu verwenden. >>> Hilfe (Typ)
Ali Afshar
8
Auch wenn Sie sich dessen nicht bewusst sind, Sie sind metaclasses dann verwenden. Typ ist eine Metaklasse, in der Tat die häufigste. :-)
Veky
5

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

class test(baseclass_with_metaclass):
    method_maker_value = "hello"

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.

David Raznick
quelle
5

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.

Charlie Martin
quelle
1
Sie benötigen auch keine Objekte, um objektorientiert zu programmieren - Sie können dies mit erstklassigen Funktionen tun. So dass Sie nicht brauchen , Objekte zu verwenden. Aber sie sind der Einfachheit halber da. Ich bin mir also nicht sicher, welchen Punkt Sie im ersten Absatz ansprechen wollen.
Tyler Crompton
1
Schau zurück auf die Frage.
Charlie Martin
4

Einige GUI-Bibliotheken haben Probleme, wenn mehrere Threads versuchen, mit ihnen zu interagieren. tkinterist 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 threadboxist, 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?

Noctis Skytower
quelle
4

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)

Mike A.
quelle
7
Ich bin mir nicht sicher, ob die Motivation in Django dieselbe war.
Ali Afshar
3

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 namensRegexLexer 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.

Benjamin Peterson
quelle
Meiner Meinung nach ist dieser Fall von Pylements einfach unnötig. Warum nicht einfach eine einfache Sammlung wie ein Diktat haben, warum eine Klasse dazu zwingen?
Ali Afshar
4
Weil eine Klasse nett die Idee von Lexer verkörpert und andere nützliche Methoden wie rate_filename () usw. hat
Benjamin Peterson
3

Ich habe Metaklassen verwendet, um Klassen einige Attribute bereitzustellen. Nehmen Sie zum Beispiel:

class NameClass(type):
    def __init__(cls, *args, **kwargs):
       type.__init__(cls, *args, **kwargs)
       cls.name = cls.__name__

legt das wird name - Attribut für jede Klasse, die den Metaklasse Satz muß NameClass zeigen.

hyperboreean
quelle
5
Ja, das funktioniert. Sie können auch eine Oberklasse verwenden, die zumindest explizit ist und im Code verfolgt werden kann. Wofür haben Sie das aus Interesse verwendet?
Ali Afshar
2

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 __superAttributs für jede Klasse und Instanz.

Eli Collins
quelle
1

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.p012017Syntax 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's DeclarativeMetain Unterklassen unterteilt , um eine Reihe von zu durchlaufen die Spalten und erstellen automatisch Modellfelder, die den Spalten entsprechen:

from sqlalchemy.ext.declarative.api import DeclarativeMeta

class CensusTableMeta(DeclarativeMeta):
    def __init__(cls, classname, bases, dict_):
        table = 'p012'
        for i in range(1, 49):
            fname = "%s%03d" % (table, i)
            dict_[fname] = Column(Integer)
            setattr(cls, fname, dict_[fname])

        super(CensusTableMeta, cls).__init__(classname, bases, dict_)

Ich könnte diese Metaklasse dann für meine Modelldefinition verwenden und auf die automatisch aufgezählten Felder des Modells zugreifen:

CensusTableBase = declarative_base(metaclass=CensusTableMeta)

class P12Tract(CensusTableBase):
    __tablename__ = 'ire_p12'

    geoid = Column(String(12), primary_key=True)

    @property
    def male_under_5(self):
        return self.p012003

    ...
Geoffrey Hing
quelle
1

Es scheint eine legitime Verwendung zu geben, die hier beschrieben wird - das Umschreiben von Python-Dokumentzeichenfolgen mit einer Metaklasse.

Mistermarko
quelle
0

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.

GeeF
quelle
0

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:

class MetaMetaclass(type):
    def __new__(meta, name, bases, attrs):
        def __new__(meta, name, bases, attrs):
            cls = type.__new__(meta, name, bases, attrs)
            cls._label = 'Made in %s' % meta.__name__
            return cls

        attrs['__new__'] = __new__
        return type.__new__(meta, name, bases, attrs)

#China is metaclass and it's __new__ method would be changed by MetaMetaclass(metaclass)
class China(MetaMetaclass, metaclass=MetaMetaclass):
    __metaclass__ = MetaMetaclass

#Taiwan is metaclass and it's __new__ method would be changed by MetaMetaclass(metaclass)
class Taiwan(MetaMetaclass, metaclass=MetaMetaclass):
    __metaclass__ = MetaMetaclass

#A is a normal class and it's __new__ method would be changed by China(metaclass)
class A(metaclass=China):
    __metaclass__ = China

#B is a normal class and it's __new__ method would be changed by Taiwan(metaclass)
class B(metaclass=Taiwan):
    __metaclass__ = Taiwan


print(A._label)  # Made in China
print(B._label)  # Made in Taiwan
  • Alles ist Objekt, also ist Klasse Objekt
  • Das Klassenobjekt wird von der Metaklasse erstellt
  • Alle vom Typ geerbten Klassen sind Metaklassen
  • Die Metaklasse könnte die Klassenerstellung steuern
  • Metaklasse könnte auch das Erstellen von Metaklassen steuern (so dass es für immer eine Schleife sein könnte)
  • Dies ist Metaprogrammierung ... Sie können das Typsystem zur Laufzeit steuern
  • Auch hier ist alles Objekt. Dies ist ein einheitliches System, Typ create type und type create instance
Dummkopf
quelle
0

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.

bleib am Leben
quelle