Was sind Metaklassen in Python?


Antworten:


2869

Eine Metaklasse ist die Klasse einer Klasse. Eine Klasse definiert, wie sich eine Instanz der Klasse (dh ein Objekt) verhält, während eine Metaklasse definiert, wie sich eine Klasse verhält. Eine Klasse ist eine Instanz einer Metaklasse.

Während Sie in Python beliebige Callables für Metaklassen verwenden können (wie Jerub zeigt), besteht der bessere Ansatz darin, es selbst zu einer tatsächlichen Klasse zu machen. typeist die übliche Metaklasse in Python. typeist selbst eine Klasse und es ist ein eigener Typ. Sie werden nicht in der Lage sein, so etwas wie typerein in Python neu zu erstellen , aber Python betrügt ein wenig. Um Ihre eigene Metaklasse in Python zu erstellen, möchten Sie wirklich nur eine Unterklasse erstellen type.

Eine Metaklasse wird am häufigsten als Klassenfabrik verwendet. Wenn Sie ein Objekt durch Aufrufen der Klasse erstellen, erstellt Python eine neue Klasse (wenn die Anweisung 'class' ausgeführt wird), indem Sie die Metaklasse aufrufen. In Kombination mit den Normalen __init__und __new__Methoden können Sie mit Metaklassen beim Erstellen einer Klasse zusätzliche Dinge tun, z. B. die neue Klasse bei einer Registrierung registrieren oder die Klasse durch etwas ganz anderes ersetzen.

Wenn die classAnweisung ausgeführt wird, führt Python zuerst den Hauptteil der classAnweisung als normalen Codeblock aus. Der resultierende Namespace (ein Diktat) enthält die Attribute der zukünftigen Klasse. Die Metaklasse wird bestimmt, indem die Basisklassen der zukünftigen Klasse (Metaklassen werden vererbt), das __metaclass__Attribut der zukünftigen Klasse (falls vorhanden) oder die __metaclass__globale Variable betrachtet werden. Die Metaklasse wird dann mit dem Namen, den Basen und den Attributen der Klasse aufgerufen, um sie zu instanziieren.

Metaklassen definieren jedoch tatsächlich den Typ einer Klasse, nicht nur eine Fabrik dafür, sodass Sie viel mehr damit tun können. Sie können beispielsweise normale Methoden für die Metaklasse definieren. Diese Metaklassenmethoden sind insofern wie Klassenmethoden, als sie für die Klasse ohne Instanz aufgerufen werden können, aber sie sind auch nicht wie Klassenmethoden, da sie nicht für eine Instanz der Klasse aufgerufen werden können. type.__subclasses__()ist ein Beispiel für eine Methode für die typeMetaklasse. Sie können auch die normale ‚Magie‘ Methoden definieren, wie __add__, __iter__und __getattr__zu implementieren oder zu ändern , wie die Klasse verhält.

Hier ist ein aggregiertes Beispiel für die einzelnen Teile:

def make_hook(f):
    """Decorator to turn 'foo' method into '__foo__'"""
    f.is_hook = 1
    return f

class MyType(type):
    def __new__(mcls, name, bases, attrs):

        if name.startswith('None'):
            return None

        # Go over attributes and see if they should be renamed.
        newattrs = {}
        for attrname, attrvalue in attrs.iteritems():
            if getattr(attrvalue, 'is_hook', 0):
                newattrs['__%s__' % attrname] = attrvalue
            else:
                newattrs[attrname] = attrvalue

        return super(MyType, mcls).__new__(mcls, name, bases, newattrs)

    def __init__(self, name, bases, attrs):
        super(MyType, self).__init__(name, bases, attrs)

        # classregistry.register(self, self.interfaces)
        print "Would register class %s now." % self

    def __add__(self, other):
        class AutoClass(self, other):
            pass
        return AutoClass
        # Alternatively, to autogenerate the classname as well as the class:
        # return type(self.__name__ + other.__name__, (self, other), {})

    def unregister(self):
        # classregistry.unregister(self)
        print "Would unregister class %s now." % self

class MyObject:
    __metaclass__ = MyType


class NoneSample(MyObject):
    pass

# Will print "NoneType None"
print type(NoneSample), repr(NoneSample)

class Example(MyObject):
    def __init__(self, value):
        self.value = value
    @make_hook
    def add(self, other):
        return self.__class__(self.value + other.value)

# Will unregister the class
Example.unregister()

inst = Example(10)
# Will fail with an AttributeError
#inst.unregister()

print inst + inst
class Sibling(MyObject):
    pass

ExampleSibling = Example + Sibling
# ExampleSibling is now a subclass of both Example and Sibling (with no
# content of its own) although it will believe it's called 'AutoClass'
print ExampleSibling
print ExampleSibling.__mro__

13
class A(type):pass<NEWLINE>class B(type,metaclass=A):pass<NEWLINE>b.__class__ = b
pppery

20
ppperry meinte er offensichtlich, dass man einen Typ nicht neu erstellen kann, ohne den Typ selbst als Metaklasse zu verwenden. Welches ist fair genug zu sagen.
Holle van

3
Sollte unregister () nicht als Instanz der Example-Klasse aufgerufen werden?
Ciasto Piekarz

5
Beachten Sie, dass __metaclass__dies in Python 3 nicht unterstützt wird. In Python 3 class MyObject(metaclass=MyType)finden Sie unter python.org/dev/peps/pep-3115 und in der folgenden Antwort.
BlackShift

2
In der Dokumentation wird beschrieben, wie die Metaklasse ausgewählt wird . Die Metaklasse wird nicht so sehr vererbt, als dass sie abgeleitet wird. Wenn Sie eine Metaklasse angeben, muss diese ein Subtyp jeder Metaklasse der Basisklasse sein. Andernfalls verwenden Sie eine Basisklassen-Metaklasse, die ein Subtyp der jeweils anderen Basisklassen-Metaklasse ist. Beachten Sie, dass möglicherweise keine gültige Metaklasse gefunden werden kann und die Definition fehlschlägt.
Chepner

6819

Klassen als Objekte

Bevor Sie Metaklassen verstehen, müssen Sie Klassen in Python beherrschen. Und Python hat eine sehr eigenartige Vorstellung davon, was Klassen sind, die aus der Smalltalk-Sprache entlehnt sind.

In den meisten Sprachen sind Klassen nur Codeteile, die beschreiben, wie ein Objekt erstellt wird. Das stimmt auch in Python:

>>> class ObjectCreator(object):
...       pass
...

>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

Aber Klassen sind mehr als das in Python. Klassen sind auch Objekte.

Ja, Objekte.

Sobald Sie das Schlüsselwort verwenden class, führt Python es aus und erstellt ein OBJEKT. Die Anleitung

>>> class ObjectCreator(object):
...       pass
...

Erstellt im Speicher ein Objekt mit dem Namen "ObjectCreator".

Dieses Objekt (die Klasse) kann selbst Objekte (die Instanzen) erstellen, und deshalb ist es eine Klasse .

Trotzdem ist es ein Objekt und daher:

  • Sie können es einer Variablen zuweisen
  • Sie können es kopieren
  • Sie können ihm Attribute hinzufügen
  • Sie können es 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 Klassen Objekte sind, können Sie sie wie jedes Objekt im laufenden Betrieb 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 Sie immer noch die ganze Klasse selbst schreiben müssen.

Da Klassen Objekte sind, müssen sie von etwas generiert werden.

Wenn Sie das classSchlüsselwort verwenden, erstellt Python dieses Objekt automatisch. Aber wie bei den meisten Dingen in Python gibt es eine Möglichkeit, dies manuell zu tun.

Erinnerst typedu dich an die Funktion ? Die gute alte Funktion, mit der Sie wissen, welcher 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, typehat eine ganz andere Fähigkeit, es kann auch Klassen im laufenden Betrieb erstellen. typekann die Beschreibung einer Klasse als Parameter verwenden und eine Klasse zurückgeben.

(Ich weiß, es ist albern, dass dieselbe Funktion je nach den von Ihnen übergebenen Parametern zwei völlig unterschiedliche Verwendungszwecke haben kann. Dies ist ein Problem aufgrund der Abwärtskompatibilität in Python.)

type funktioniert so:

type(name, bases, attrs)

Wo:

  • name: Name der Klasse
  • bases: Tupel der übergeordneten Klasse (für die Vererbung kann leer sein)
  • attrs: Wörterbuch mit Attributnamen und -werten

z.B:

>>> class MyShinyClass(object):
...       pass

kann manuell auf folgende Weise 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 Namen der Klasse und als Variable für die Klassenreferenz verwenden. Sie können unterschiedlich sein, aber es gibt keinen Grund, die Dinge zu komplizieren.

typeAkzeptiert ein Wörterbuch, um die Attribute der Klasse zu definieren. Damit:

>>> class Foo(object):
...       bar = True

Kann übersetzt werden in:

>>> 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 können Sie davon erben, 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 möchten Sie Ihrer Klasse Methoden hinzufügen. 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 nach dem dynamischen Erstellen der Klasse noch mehr Methoden hinzufügen, genau wie Sie einem normal erstellten Klassenobjekt Methoden 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, wohin wir gehen: In Python sind Klassen Objekte, und Sie können eine Klasse im laufenden Betrieb dynamisch erstellen.

Dies ist, was Python tut, wenn Sie das Schlüsselwort verwenden class, und dies unter Verwendung einer Metaklasse.

Was sind Metaklassen (endlich)

Metaklassen sind das Zeug, das Klassen erstellt.

Sie definieren Klassen, um Objekte zu erstellen, oder?

Wir haben jedoch gelernt, dass Python-Klassen Objekte sind.

Nun, Metaklassen schaffen diese Objekte. Sie sind die Klassen der Klassen, Sie können sie sich so vorstellen:

MyClass = MetaClass()
my_object = MyClass()

Sie haben gesehen, dass typeSie so etwas tun können:

MyClass = type('MyClass', (), {})

Das liegt daran, dass die Funktion typetatsächlich eine Metaklasse ist. typeist die Metaklasse, mit der Python alle Klassen hinter den Kulissen erstellt.

Jetzt fragen Sie sich, warum zum Teufel es in Kleinbuchstaben geschrieben ist und nicht Type?

Nun, ich denke, es ist eine Frage der Konsistenz mit strder Klasse, die Zeichenfolgenobjekte erstellt, und intder Klasse, die Ganzzahlobjekte erstellt. typeist nur die Klasse, die Klassenobjekte erstellt.

Sie sehen das, indem Sie das __class__Attribut überprüfen .

Alles, und ich meine alles, ist ein Objekt in Python. Dazu gehören Ints, Strings, Funktionen und Klassen. Alle von ihnen sind Objekte. Und alle 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'>

Was ist das __class__von irgendjemandem __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 genau das, was Klassenobjekte erstellt.

Sie können es eine "Klassenfabrik" nennen, wenn Sie möchten.

type ist die integrierte Metaklasse, die Python verwendet, aber Sie können natürlich Ihre eigene Metaklasse erstellen.

Das __metaclass__Attribut

In Python 2 können Sie __metaclass__beim Schreiben einer Klasse ein Attribut hinzufügen (Informationen zur Python 3-Syntax finden Sie im nächsten Abschnitt):

class Foo(object):
    __metaclass__ = something...
    [...]

In diesem Fall verwendet Python die Metaklasse, um die Klasse zu erstellen Foo.

Vorsicht, es ist schwierig.

Sie schreiben class Foo(object)zuerst, aber das Klassenobjekt Foowird noch nicht im Speicher erstellt.

Python sucht __metaclass__in der Klassendefinition. Wenn es es findet, wird es verwendet, um die Objektklasse zu erstellen Foo. Wenn dies nicht der Fall ist, wird typedie Klasse erstellt.

Lies das mehrmals.

Wenn Sie das tun:

class Foo(Bar):
    pass

Python macht Folgendes:

Gibt es ein __metaclass__Attribut in Foo?

Wenn ja, erstellen Sie im Speicher ein Klassenobjekt (ich sagte ein Klassenobjekt, bleiben Sie hier bei mir), mit dem Namen, Fooindem Sie das verwenden, was sich darin befindet __metaclass__.

Wenn Python nicht finden kann, sucht __metaclass__es __metaclass__auf MODULE-Ebene nach einem und versucht, dasselbe zu tun (jedoch nur für Klassen, die nichts erben, im Grunde alte Klassen).

Wenn es dann überhaupt keine findet __metaclass__, verwendet es die Bareigene Metaklasse (die erste übergeordnete) (möglicherweise die Standardeinstellung type), um das Klassenobjekt zu erstellen.

Achten Sie hier darauf, dass das __metaclass__Attribut nicht vererbt wird, sondern die Metaklasse von parent ( Bar.__class__). Wenn Barein __metaclass__Attribut verwendet wird, das Barmit type()(und nicht type.__new__()) erstellt wurde, erben die Unterklassen dieses Verhalten nicht.

Die große Frage ist nun, was können Sie eingeben __metaclass__?

Die Antwort lautet: etwas, das eine Klasse erstellen kann.

Und was kann eine Klasse schaffen? typeoder irgendetwas, das es unterordnet oder verwendet.

Metaklassen in Python 3

Die Syntax zum Festlegen der Metaklasse wurde in Python 3 geändert:

class Foo(object, metaclass=something):
    ...

Das heißt, das __metaclass__Attribut wird nicht mehr zugunsten eines Schlüsselwortarguments in der Liste der Basisklassen verwendet.

Das Verhalten von Metaklassen bleibt jedoch weitgehend gleich .

Eine Sache, die Metaklassen in Python 3 hinzugefügt wurde, ist, dass Sie Attribute auch als Schlüsselwortargumente an eine Metaklasse übergeben können, wie folgt:

class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
    ...

Lesen Sie den folgenden Abschnitt, um zu erfahren, wie Python damit umgeht.

Benutzerdefinierte Metaklassen

Der Hauptzweck einer Metaklasse besteht darin, die Klasse beim Erstellen automatisch zu ändern.

Dies tun Sie normalerweise für APIs, bei denen Sie Klassen erstellen möchten, die dem aktuellen Kontext entsprechen.

Stellen Sie sich ein dummes Beispiel vor, in dem Sie entscheiden, dass alle Klassen in Ihrem Modul ihre Attribute in Großbuchstaben schreiben sollen. Es gibt verschiedene Möglichkeiten, dies zu tun, aber eine Möglichkeit besteht darin, __metaclass__auf Modulebene festzulegen.

Auf diese Weise werden alle Klassen dieses Moduls mit dieser Metaklasse erstellt, und wir müssen der Metaklasse lediglich mitteilen, dass alle Attribute in Großbuchstaben umgewandelt werden sollen.

Glücklicherweise __metaclass__kann es tatsächlich jede aufrufbare Klasse sein, es muss keine formelle Klasse sein (ich weiß, etwas mit 'Klasse' im Namen muss keine Klasse sein, mach eine Figur ... 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'

Lass uns nachsehen:

>>> hasattr(Foo, 'bar')
False
>>> hasattr(Foo, 'BAR')
True
>>> Foo.BAR
'bip'

Lassen Sie uns jetzt genau das Gleiche tun, aber eine echte Klasse für eine 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 das Obige umschreiben, aber mit kürzeren und realistischeren Variablennamen, jetzt wo wir 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)

Möglicherweise haben Sie das zusätzliche Argument bemerkt cls. Es gibt nichts Besonderes: __new__Empfängt immer die Klasse, in der es definiert ist, als ersten Parameter. Genau wie selfbei normalen Methoden, die die Instanz als ersten Parameter erhalten, oder der definierenden Klasse für Klassenmethoden.

Aber das ist nicht richtig OOP. Wir rufen typedirekt an und überschreiben oder rufen nicht die der Eltern an __new__. Lassen Sie uns das 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 verwenden super, was die Vererbung erleichtert (denn ja, Sie können Metaklassen haben, die von Metaklassen erben, vom 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üsselwortargumenten wie folgt ausführen:

class Foo(object, metaclass=MyMetaclass, kwarg1=value1):
    ...

Dies wird in der Metaklasse übersetzt, um es zu verwenden:

class MyMetaclass(type):
    def __new__(cls, clsname, bases, dct, kwargs1=default):
        ...

Das ist es. Metaklassen haben wirklich nichts mehr zu bieten.

Der Grund für die Komplexität des Codes bei der Verwendung von Metaklassen liegt nicht in Metaklassen, sondern darin, dass Sie normalerweise Metaklassen verwenden, um verdrehte Dinge zu erledigen, die auf Introspektion, Manipulation der Vererbung, Variablen wie __dict__usw. beruhen .

In der Tat sind Metaklassen besonders nützlich, um schwarze Magie und damit komplizierte Dinge zu tun. Aber für sich sind sie einfach:

  • eine Klassenerstellung abfangen
  • Ändern Sie die Klasse
  • Geben Sie die geänderte Klasse zurück

Warum sollten Sie Metaklassenklassen anstelle von Funktionen verwenden?

Da __metaclass__jede aufrufbare annehmen können, warum sollten Sie eine Klasse verwenden , da ist es offensichtlich kompliziert mehr?

Dafü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. Metaklasse kann von Metaklasse erben und übergeordnete Methoden überschreiben. Metaklassen können sogar Metaklassen verwenden.
  • Unterklassen einer Klasse sind Instanzen ihrer Metaklasse, wenn Sie eine Metaklassenklasse angegeben haben, jedoch keine Metaklassenfunktion.
  • Sie können Ihren Code besser strukturieren. Sie verwenden niemals Metaklassen für etwas so Triviales wie das obige Beispiel. Es ist normalerweise für etwas Kompliziertes. Die Möglichkeit, mehrere Methoden zu erstellen und in einer Klasse zu gruppieren, ist sehr nützlich, um das Lesen des Codes zu erleichtern.
  • Sie können auf Haken __new__, __init__und __call__. So können Sie verschiedene Dinge tun. Selbst wenn Sie normalerweise alles erledigen können __new__, sind einige Leute einfach komfortabler __init__.
  • Diese werden Metaklassen genannt, verdammt! Es muss etwas bedeuten!

Warum sollten Sie Metaklassen verwenden?

Nun die große Frage. Warum sollten Sie eine obskure fehleranfällige Funktion verwenden?

Normalerweise tun Sie das nicht:

Metaklassen sind tiefere Magie, über die sich 99% der Benutzer niemals Sorgen machen sollten. Wenn Sie sich fragen, ob Sie sie brauchen, tun Sie das nicht (die Menschen, die sie tatsächlich brauchen, wissen mit Sicherheit, dass sie sie brauchen, und brauchen keine Erklärung, warum).

Python Guru Tim Peters

Der Hauptanwendungsfall für eine Metaklasse ist das Erstellen einer API. Ein typisches Beispiel hierfür ist das Django ORM. Hier können Sie Folgendes definieren:

class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()

Aber wenn Sie dies tun:

person = Person(name='bob', age='35')
print(person.age)

Es wird kein IntegerFieldObjekt zurückgegeben. Es gibt ein zurück intund kann es sogar direkt aus der Datenbank übernehmen.

Dies ist möglich, weil models.Modeldefiniert __metaclass__und es etwas Magie verwendet, die Persondas soeben definierte mit einfachen Anweisungen in einen komplexen Hook für ein Datenbankfeld verwandelt .

Django lässt etwas Komplexes einfach aussehen, indem es eine einfache API verfügbar macht und Metaklassen verwendet. Dabei wird Code aus dieser API neu erstellt, um die eigentliche Aufgabe hinter den Kulissen zu erledigen.

Das letzte Wort

Erstens wissen Sie, dass Klassen Objekte sind, die Instanzen erstellen können.

In der Tat sind Klassen selbst Instanzen. Von Metaklassen.

>>> class Foo(object): pass
>>> id(Foo)
142630324

Alles ist ein Objekt in Python, und alle sind entweder Instanzen von Klassen oder Instanzen von Metaklassen.

Außer type.

typeist eigentlich eine eigene Metaklasse. Dies ist nichts, was Sie in reinem Python reproduzieren könnten, und dies geschieht durch ein wenig Betrug auf der Implementierungsebene.

Zweitens sind Metaklassen kompliziert. Möglicherweise möchten Sie sie nicht für sehr einfache Klassenänderungen verwenden. Sie können Klassen mit zwei verschiedenen Techniken ändern:

In 99% der Fälle, in denen Sie eine Klassenänderung benötigen, ist es besser, diese zu verwenden.

In 98% der Fälle müssen Sie jedoch überhaupt keine Klassenänderungen vornehmen.


30
Es scheint, dass es in Django models.Modelnicht verwendet __metaclass__, sondern class Model(metaclass=ModelBase):auf eine ModelBaseKlasse verweist , die dann die oben erwähnte Metaklassenmagie ausführt. Guter Eintrag! Hier ist die Django-Quelle: github.com/django/django/blob/master/django/db/models/…
Max Goodridge

15
<< Achten Sie hier darauf, dass das __metaclass__Attribut nicht vererbt wird, sondern die Metaklasse von parent ( Bar.__class__). Wenn Barein __metaclass__Attribut verwendet wird, das Barmit type()(und nicht type.__new__()) erstellt wurde, erben die Unterklassen dieses Verhalten nicht. >> - Könnten Sie / jemand diese Passage etwas näher erläutern?
Petrux

15
@MaxGoodridge Das ist die Python 3-Syntax für Metaklassen. Siehe Python 3.6 Datenmodell VS Python 2.7 Datenmodell
TBBle

2
Now you wonder why the heck is it written in lowercase, and not Type?- Nun, weil es in C implementiert ist - es ist der gleiche Grund, warum defaultdict in Kleinbuchstaben geschrieben ist, während OrderedDict (in Python 2) normal ist. CamelCase
Mr_and_Mrs_D

15
Es handelt sich um eine Community-Wiki-Antwort (daher können diejenigen, die mit Korrekturen / Verbesserungen kommentiert haben, in Betracht ziehen, ihre Kommentare in der Antwort zu bearbeiten, wenn sie sicher sind, dass sie korrekt sind).
Brōtsyorfuzthrāx

403

Beachten Sie, dass diese Antwort für Python 2.x gilt, wie es 2008 geschrieben wurde. Metaklassen unterscheiden sich in 3.x geringfügig.

Metaklassen sind die geheime Sauce, mit der „Klasse“ funktioniert. Die Standardmetaklasse für ein neues Stilobjekt heißt "Typ".

class type(object)
  |  type(object) -> the object's type
  |  type(name, bases, dict) -> a new type

Metaklassen benötigen 3 Argumente. ' Name ', ' Basen ' und ' Diktat '

Hier beginnt das Geheimnis. Suchen Sie in dieser Beispielklassendefinition, woher Name, Basen und das Diktat stammen.

class ThisIsTheName(Bases, Are, Here):
    All_the_code_here
    def doesIs(create, a):
        dict

Definieren wir eine Metaklasse, die zeigt, wie ' class: ' sie aufruft.

def test_metaclass(name, bases, dict):
    print 'The Class Name is', name
    print 'The Class Bases are', bases
    print 'The dict has', len(dict), 'elems, the keys are', dict.keys()

    return "yellow"

class TestName(object, None, int, 1):
    __metaclass__ = test_metaclass
    foo = 1
    def baz(self, arr):
        pass

print 'TestName = ', repr(TestName)

# output => 
The Class Name is TestName
The Class Bases are (<type 'object'>, None, <type 'int'>, 1)
The dict has 4 elems, the keys are ['baz', '__module__', 'foo', '__metaclass__']
TestName =  'yellow'

Und jetzt, ein Beispiel, das tatsächlich etwas bedeutet, werden die Variablen in der Liste "Attribute" automatisch für die Klasse festgelegt und auf "Keine" gesetzt.

def init_attributes(name, bases, dict):
    if 'attributes' in dict:
        for attr in dict['attributes']:
            dict[attr] = None

    return type(name, bases, dict)

class Initialised(object):
    __metaclass__ = init_attributes
    attributes = ['foo', 'bar', 'baz']

print 'foo =>', Initialised.foo
# output=>
foo => None

Beachten Sie, dass das magische Verhalten, Initialiseddas durch die Metaklasse init_attributesentsteht, nicht an eine Unterklasse von übergeben wird Initialised.

Hier ist ein noch konkreteres Beispiel, das zeigt, wie Sie 'Typ' in eine Unterklasse umwandeln können, um eine Metaklasse zu erstellen, die beim Erstellen der Klasse eine Aktion ausführt. Das ist ziemlich schwierig:

class MetaSingleton(type):
    instance = None
    def __call__(cls, *args, **kw):
        if cls.instance is None:
            cls.instance = super(MetaSingleton, cls).__call__(*args, **kw)
        return cls.instance

class Foo(object):
    __metaclass__ = MetaSingleton

a = Foo()
b = Foo()
assert a is b

169

Andere haben erklärt, wie Metaklassen funktionieren und wie sie in das Python-Typsystem passen. Hier ist ein Beispiel dafür, wofür sie verwendet werden können. In einem von mir geschriebenen Testframework wollte ich die Reihenfolge verfolgen, in der Klassen definiert wurden, damit ich sie später in dieser Reihenfolge instanziieren kann. Ich fand es am einfachsten, dies mit einer Metaklasse zu tun.

class MyMeta(type):

    counter = 0

    def __init__(cls, name, bases, dic):
        type.__init__(cls, name, bases, dic)
        cls._order = MyMeta.counter
        MyMeta.counter += 1

class MyType(object):              # Python 2
    __metaclass__ = MyMeta

class MyType(metaclass=MyMeta):    # Python 3
    pass

Alles, was eine Unterklasse von MyTypeist, erhält dann ein Klassenattribut, _orderdas die Reihenfolge aufzeichnet, in der die Klassen definiert wurden.


Danke für das Beispiel. Warum war das für Sie einfacher als das Erben von MyBase, dessen __init__(self)Aussage type(self)._order = MyBase.counter; MyBase.counter += 1?
Michael Gundlach

1
Ich wollte, dass die Klassen selbst und nicht ihre Instanzen nummeriert werden.
Kindall

Richtig, duh. Vielen Dank. Mein Code würde das Attribut von MyType bei jeder Instanziierung zurücksetzen und das Attribut niemals festlegen, wenn niemals eine Instanz von MyType erstellt würde. Hoppla. (Und eine Klasseneigenschaft könnte auch funktionieren, aber im Gegensatz zur Metaklasse bietet sie keinen offensichtlichen Platz zum Aufbewahren der Theke.)
Michael Gundlach

1
Dies ist ein sehr interessantes Beispiel, nicht zuletzt, weil man wirklich sehen kann, warum eine Metaklasse damit benötigt werden könnte, um eine Lösung für eine bestimmte Schwierigkeit zu finden. OTOH Ich habe Mühe, davon überzeugt zu sein, dass jeder wirklich Objekte in der Reihenfolge instanziieren muss, in der seine Klassen definiert wurden: Ich denke, wir müssen nur Ihr Wort dafür nehmen :).
Mike Nagetier

159

Eine Verwendung für Metaklassen besteht darin, einer Instanz automatisch neue Eigenschaften und Methoden hinzuzufügen.

Wenn Sie sich beispielsweise Django-Modelle ansehen , sieht ihre Definition etwas verwirrend aus. Es sieht so aus, als würden Sie nur Klasseneigenschaften definieren:

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

Zur Laufzeit werden die Personenobjekte jedoch mit allen möglichen nützlichen Methoden gefüllt. In der Quelle finden Sie einige erstaunliche Metaclassery.


6
Fügt die Verwendung von Metaklassen einer Klasse und nicht einer Instanz nicht neue Eigenschaften und Methoden hinzu ? Soweit ich es verstanden habe, verändert die Meta-Klasse die Klasse selbst und daher können die Instanzen von der geänderten Klasse unterschiedlich konstruiert werden. Könnte für Leute, die versuchen, die Natur einer Metaklasse zu verstehen, etwas irreführend sein. Nützliche Methoden für Instanzen können durch normale Inhärenz erreicht werden. Der Verweis auf Django-Code als Beispiel ist jedoch gut.
Trixn

119

Ich denke, die ONLamp-Einführung in die Metaklassenprogrammierung ist gut geschrieben und bietet eine wirklich gute Einführung in das Thema, obwohl sie bereits mehrere Jahre alt ist.

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html (archiviert unter https://web.archive.org/web/20080206005253/http://www.onlamp. com / pub / a / python / 2003/04/17 / metaclasses.html )

Kurz gesagt: Eine Klasse ist eine Blaupause für die Erstellung einer Instanz, eine Metaklasse ist eine Blaupause für die Erstellung einer Klasse. Es ist leicht zu erkennen, dass in Python-Klassen auch erstklassige Objekte sein müssen, um dieses Verhalten zu ermöglichen.

Ich habe selbst noch nie eine geschrieben, aber ich denke, eine der schönsten Anwendungen von Metaklassen ist im Django-Framework zu sehen . Die Modellklassen verwenden einen Metaklassenansatz, um einen deklarativen Stil zum Schreiben neuer Modelle oder Formularklassen zu ermöglichen. Während die Metaklasse die Klasse erstellt, haben alle Mitglieder die Möglichkeit, die Klasse selbst anzupassen.

Die Sache, die noch zu sagen bleibt, ist: Wenn Sie nicht wissen, was Metaklassen sind, beträgt die Wahrscheinlichkeit, dass Sie sie nicht benötigen, 99%.


109

Was sind Metaklassen? Wofür benutzt du sie?

TLDR: Eine Metaklasse instanziiert und definiert das Verhalten einer Klasse genau wie eine Klasse das Verhalten einer Instanz.

Pseudocode:

>>> Class(...)
instance

Das Obige sollte vertraut aussehen. Woher kommt Classdas? Es ist eine Instanz einer Metaklasse (auch Pseudocode):

>>> Metaclass(...)
Class

In echtem Code können wir die Standard-Metaklasse übergeben, typealles, was wir zum Instanziieren einer Klasse benötigen, und wir erhalten eine Klasse:

>>> type('Foo', (object,), {}) # requires a name, bases, and a namespace
<class '__main__.Foo'>

Anders ausgedrückt

  • Eine Klasse gehört zu einer Instanz wie eine Metaklasse zu einer Klasse.

    Wenn wir ein Objekt instanziieren, erhalten wir eine Instanz:

    >>> object()                          # instantiation of class
    <object object at 0x7f9069b4e0b0>     # instance

    Wenn wir eine Klasse explizit mit der Standard-Metaklasse definieren, typeinstanziieren wir sie ebenfalls:

    >>> type('Object', (object,), {})     # instantiation of metaclass
    <class '__main__.Object'>             # instance
  • Anders ausgedrückt, eine Klasse ist eine Instanz einer Metaklasse:

    >>> isinstance(object, type)
    True
  • Ein dritter Weg, eine Metaklasse ist die Klasse einer Klasse.

    >>> type(object) == type
    True
    >>> object.__class__
    <class 'type'>

Wenn Sie eine Klassendefinition schreiben und Python sie ausführt, wird das Klassenobjekt mithilfe einer Metaklasse instanziiert (die wiederum zum Instanziieren von Instanzen dieser Klasse verwendet wird).

So wie wir Klassendefinitionen verwenden können, um das Verhalten benutzerdefinierter Objektinstanzen zu ändern, können wir eine Metaklassen-Klassendefinition verwenden, um das Verhalten eines Klassenobjekts zu ändern.

Wofür können sie verwendet werden? Aus den Dokumenten :

Die Verwendungsmöglichkeiten für Metaklassen sind grenzenlos. Einige der untersuchten Ideen umfassen Protokollierung, Schnittstellenprüfung, automatische Delegierung, automatische Erstellung von Eigenschaften, Proxys, Frameworks und automatische Ressourcensperrung / -synchronisierung.

Dennoch wird Benutzern normalerweise empfohlen, die Verwendung von Metaklassen zu vermeiden, sofern dies nicht unbedingt erforderlich ist.

Sie verwenden jedes Mal eine Metaklasse, wenn Sie eine Klasse erstellen:

Wenn Sie beispielsweise eine Klassendefinition schreiben,

class Foo(object): 
    'demo'

Sie instanziieren ein Klassenobjekt.

>>> Foo
<class '__main__.Foo'>
>>> isinstance(Foo, type), isinstance(Foo, object)
(True, True)

typeDies entspricht dem funktionalen Aufrufen mit den entsprechenden Argumenten und dem Zuweisen des Ergebnisses zu einer Variablen mit diesem Namen:

name = 'Foo'
bases = (object,)
namespace = {'__doc__': 'demo'}
Foo = type(name, bases, namespace)

Beachten Sie, dass einige Dinge automatisch __dict__zum Namespace hinzugefügt werden :

>>> Foo.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Foo' objects>, 
'__module__': '__main__', '__weakref__': <attribute '__weakref__' 
of 'Foo' objects>, '__doc__': 'demo'})

Die Metaklasse des von uns erstellten Objekts ist in beiden Fällen type.

(Seite A-Note auf dem Inhalt der Klasse __dict__: __module__ist da , weil Klassen müssen wissen , wo sie definiert sind, und , __dict__und __weakref__es gibt , weil wir nicht definieren __slots__- wenn wir definieren__slots__ wir in den Fällen , ein wenig Platz sparen werden, wie wir können sie verbieten __dict__und __weakref__ausschließen. Zum Beispiel:

>>> Baz = type('Bar', (object,), {'__doc__': 'demo', '__slots__': ()})
>>> Baz.__dict__
mappingproxy({'__doc__': 'demo', '__slots__': (), '__module__': '__main__'})

... Aber ich schweife ab.)

Wir können typewie jede andere Klassendefinition erweitern:

Hier ist die Standardeinstellung __repr__für Klassen:

>>> Foo
<class '__main__.Foo'>

Eines der wertvollsten Dinge, die wir beim Schreiben eines Python-Objekts standardmäßig tun können, ist, es mit einem Gut zu versehen __repr__. Wenn wir anrufen help(repr), erfahren wir, dass es einen guten Test für einen gibt __repr__, der auch einen Test für Gleichheit erfordert - obj == eval(repr(obj)). Die folgende einfache Implementierung von __repr__und __eq__für Klasseninstanzen unserer Typklasse bietet uns eine Demonstration, die den Standard __repr__von Klassen verbessern kann :

class Type(type):
    def __repr__(cls):
        """
        >>> Baz
        Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
        >>> eval(repr(Baz))
        Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
        """
        metaname = type(cls).__name__
        name = cls.__name__
        parents = ', '.join(b.__name__ for b in cls.__bases__)
        if parents:
            parents += ','
        namespace = ', '.join(': '.join(
          (repr(k), repr(v) if not isinstance(v, type) else v.__name__))
               for k, v in cls.__dict__.items())
        return '{0}(\'{1}\', ({2}), {{{3}}})'.format(metaname, name, parents, namespace)
    def __eq__(cls, other):
        """
        >>> Baz == eval(repr(Baz))
        True            
        """
        return (cls.__name__, cls.__bases__, cls.__dict__) == (
                other.__name__, other.__bases__, other.__dict__)

Wenn wir nun ein Objekt mit dieser Metaklasse erstellen, bietet das __repr__Echo in der Befehlszeile einen viel weniger hässlichen Anblick als die Standardeinstellung:

>>> class Bar(object): pass
>>> Baz = Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
>>> Baz
Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})

Mit einer __repr__für die Klasseninstanz definierten Funktion können wir unseren Code besser debuggen. Eine weitere Überprüfung mit eval(repr(Class))ist jedoch unwahrscheinlich (da Funktionen von ihren Standardeinstellungen eher nicht bewertet werden können __repr__).

Eine erwartete Verwendung: __prepare__ein Namespace

Wenn wir beispielsweise wissen möchten, in welcher Reihenfolge die Methoden einer Klasse erstellt werden, können wir ein geordnetes Diktat als Namespace der Klasse angeben. Wir würden dies tun, mit __prepare__dem das Namespace-Diktat für die Klasse zurückgegeben wird, wenn es in Python 3 implementiert ist :

from collections import OrderedDict

class OrderedType(Type):
    @classmethod
    def __prepare__(metacls, name, bases, **kwargs):
        return OrderedDict()
    def __new__(cls, name, bases, namespace, **kwargs):
        result = Type.__new__(cls, name, bases, dict(namespace))
        result.members = tuple(namespace)
        return result

Und Verwendung:

class OrderedMethodsObject(object, metaclass=OrderedType):
    def method1(self): pass
    def method2(self): pass
    def method3(self): pass
    def method4(self): pass

Und jetzt haben wir eine Aufzeichnung der Reihenfolge, in der diese Methoden (und andere Klassenattribute) erstellt wurden:

>>> OrderedMethodsObject.members
('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4')

Beachten Sie, dass dieses Beispiel aus der Dokumentation übernommen wurde - die neue Aufzählung in der Standardbibliothek übernimmt dies.

Wir haben also eine Metaklasse instanziiert, indem wir eine Klasse erstellt haben. Wir können die Metaklasse auch wie jede andere Klasse behandeln. Es hat eine Reihenfolge der Methodenauflösung:

>>> inspect.getmro(OrderedType)
(<class '__main__.OrderedType'>, <class '__main__.Type'>, <class 'type'>, <class 'object'>)

Und es hat ungefähr das Richtige repr(was wir nicht mehr bewerten können, wenn wir keinen Weg finden, unsere Funktionen darzustellen):

>>> OrderedMethodsObject
OrderedType('OrderedMethodsObject', (object,), {'method1': <function OrderedMethodsObject.method1 at 0x0000000002DB01E0>, 'members': ('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4'), 'method3': <function OrderedMet
hodsObject.method3 at 0x0000000002DB02F0>, 'method2': <function OrderedMethodsObject.method2 at 0x0000000002DB0268>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'OrderedMethodsObject' objects>, '__doc__': None, '__d
ict__': <attribute '__dict__' of 'OrderedMethodsObject' objects>, 'method4': <function OrderedMethodsObject.method4 at 0x0000000002DB0378>})

78

Python 3-Update

Es gibt (zu diesem Zeitpunkt) zwei Schlüsselmethoden in einer Metaklasse:

  • __prepare__, und
  • __new__

__prepare__Mit dieser Option können Sie eine benutzerdefinierte Zuordnung (z. B. eine OrderedDict) angeben, die als Namespace verwendet wird, während die Klasse erstellt wird. Sie müssen eine Instanz eines beliebigen Namespace zurückgeben. Wenn Sie keine __prepare__normale implementieren, dictwird verwendet.

__new__ ist verantwortlich für die eigentliche Erstellung / Änderung der endgültigen Klasse.

Eine nackte Metaklasse, die nichts mehr tut, möchte:

class Meta(type):

    def __prepare__(metaclass, cls, bases):
        return dict()

    def __new__(metacls, cls, bases, clsdict):
        return super().__new__(metacls, cls, bases, clsdict)

Ein einfaches Beispiel:

Angenommen, Sie möchten, dass ein einfacher Validierungscode für Ihre Attribute ausgeführt wird - so wie es immer ein intoder ein sein muss str. Ohne eine Metaklasse würde Ihre Klasse ungefähr so ​​aussehen:

class Person:
    weight = ValidateType('weight', int)
    age = ValidateType('age', int)
    name = ValidateType('name', str)

Wie Sie sehen, müssen Sie den Namen des Attributs zweimal wiederholen. Dies ermöglicht Tippfehler zusammen mit irritierenden Fehlern.

Eine einfache Metaklasse kann dieses Problem lösen:

class Person(metaclass=Validator):
    weight = ValidateType(int)
    age = ValidateType(int)
    name = ValidateType(str)

So würde die Metaklasse aussehen (nicht verwendet, __prepare__da sie nicht benötigt wird):

class Validator(type):
    def __new__(metacls, cls, bases, clsdict):
        # search clsdict looking for ValidateType descriptors
        for name, attr in clsdict.items():
            if isinstance(attr, ValidateType):
                attr.name = name
                attr.attr = '_' + name
        # create final class and return it
        return super().__new__(metacls, cls, bases, clsdict)

Ein Probelauf von:

p = Person()
p.weight = 9
print(p.weight)
p.weight = '9'

produziert:

9
Traceback (most recent call last):
  File "simple_meta.py", line 36, in <module>
    p.weight = '9'
  File "simple_meta.py", line 24, in __set__
    (self.name, self.type, value))
TypeError: weight must be of type(s) <class 'int'> (got '9')

Hinweis : Dieses Beispiel ist einfach genug, es hätte auch mit einem Klassendekorateur durchgeführt werden können, aber vermutlich würde eine tatsächliche Metaklasse viel mehr bewirken.

Die 'ValidateType'-Klasse als Referenz:

class ValidateType:
    def __init__(self, type):
        self.name = None  # will be set by metaclass
        self.attr = None  # will be set by metaclass
        self.type = type
    def __get__(self, inst, cls):
        if inst is None:
            return self
        else:
            return inst.__dict__[self.attr]
    def __set__(self, inst, value):
        if not isinstance(value, self.type):
            raise TypeError('%s must be of type(s) %s (got %r)' %
                    (self.name, self.type, value))
        else:
            inst.__dict__[self.attr] = value

Wow, dies ist eine großartige neue Funktion, von der ich nicht wusste, dass sie in Python 3 existiert. Vielen Dank für das Beispiel!
Rich Lysakowski PhD

Beachten Sie, dass Sie seit Python 3.6 __set_name__(cls, name)im Deskriptor ( ValidateType) den Namen im Deskriptor ( self.nameund in diesem Fall auch self.attr) festlegen können . Dies wurde hinzugefügt, um für diesen speziellen Anwendungsfall nicht in Metaklassen eintauchen zu müssen (siehe PEP 487).
Lars

68

Rolle einer Metaklassenmethode __call__()beim Erstellen einer Klasseninstanz

Wenn Sie Python länger als ein paar Monate programmiert haben, werden Sie schließlich auf Code stoßen, der so aussieht:

# define a class
class SomeClass(object):
    # ...
    # some definition here ...
    # ...

# create an instance of it
instance = SomeClass()

# then call the object as if it's a function
result = instance('foo', 'bar')

Letzteres ist möglich, wenn Sie die __call__()magische Methode in der Klasse implementieren .

class SomeClass(object):
    # ...
    # some definition here ...
    # ...

    def __call__(self, foo, bar):
        return bar + foo

Die __call__()Methode wird aufgerufen, wenn eine Instanz einer Klasse als aufrufbar verwendet wird. Wie wir aus früheren Antworten gesehen haben, ist eine Klasse selbst eine Instanz einer Metaklasse. Wenn wir also die Klasse als aufrufbare Klasse verwenden (dh wenn wir eine Instanz davon erstellen), rufen wir tatsächlich die __call__()Methode ihrer Metaklasse auf . Zu diesem Zeitpunkt sind die meisten Python-Programmierer etwas verwirrt, da ihnen mitgeteilt wurde, dass Sie beim Erstellen einer solchen Instanz instance = SomeClass()deren __init__()Methode aufrufen . Einige, die etwas tiefer gegraben haben, wissen das, bevor __init__()es das gibt __new__(). Nun, heute wird eine weitere Ebene der Wahrheit enthüllt, bevor __new__()es die Metaklasse gibt __call__().

Lassen Sie uns die Methodenaufrufkette unter dem Gesichtspunkt der Erstellung einer Instanz einer Klasse untersuchen.

Dies ist eine Metaklasse, die genau in dem Moment protokolliert, bevor eine Instanz erstellt wird und in dem sie zurückgegeben werden soll.

class Meta_1(type):
    def __call__(cls):
        print "Meta_1.__call__() before creating an instance of ", cls
        instance = super(Meta_1, cls).__call__()
        print "Meta_1.__call__() about to return instance."
        return instance

Dies ist eine Klasse, die diese Metaklasse verwendet

class Class_1(object):

    __metaclass__ = Meta_1

    def __new__(cls):
        print "Class_1.__new__() before creating an instance."
        instance = super(Class_1, cls).__new__(cls)
        print "Class_1.__new__() about to return instance."
        return instance

    def __init__(self):
        print "entering Class_1.__init__() for instance initialization."
        super(Class_1,self).__init__()
        print "exiting Class_1.__init__()."

Und jetzt erstellen wir eine Instanz von Class_1

instance = Class_1()
# Meta_1.__call__() before creating an instance of <class '__main__.Class_1'>.
# Class_1.__new__() before creating an instance.
# Class_1.__new__() about to return instance.
# entering Class_1.__init__() for instance initialization.
# exiting Class_1.__init__().
# Meta_1.__call__() about to return instance.

Beachten Sie, dass der obige Code nichts anderes tut, als die Aufgaben zu protokollieren. Jede Methode delegiert die eigentliche Arbeit an die Implementierung ihres übergeordneten Elements, wodurch das Standardverhalten beibehalten wird. Da die übergeordnete Klasse von typeis Meta_1( typedie standardmäßige übergeordnete Metaklasse) ist und die Reihenfolge der obigen Ausgabe berücksichtigt wird, haben wir jetzt einen Hinweis darauf, was die Pseudoimplementierung von type.__call__():

class type:
    def __call__(cls, *args, **kwarg):

        # ... maybe a few things done to cls here

        # then we call __new__() on the class to create an instance
        instance = cls.__new__(cls, *args, **kwargs)

        # ... maybe a few things done to the instance here

        # then we initialize the instance with its __init__() method
        instance.__init__(*args, **kwargs)

        # ... maybe a few more things done to instance here

        # then we return it
        return instance

Wir können sehen, dass die __call__()Methode der Metaklasse diejenige ist, die zuerst aufgerufen wird. Anschließend wird die Erstellung der Instanz an die __new__()Methode der Klasse und die Initialisierung an die Instanz delegiert __init__(). Es ist auch derjenige, der letztendlich die Instanz zurückgibt.

Aus dem oben Gesagten ergibt sich, dass der Metaklasse __call__()auch die Möglichkeit gegeben wird, zu entscheiden, ob ein Anruf bei Class_1.__new__()oder Class_1.__init__()irgendwann getätigt wird oder nicht . Während der Ausführung könnte tatsächlich ein Objekt zurückgegeben werden, das von keiner dieser Methoden berührt wurde. Nehmen Sie zum Beispiel diesen Ansatz für das Singleton-Muster:

class Meta_2(type):
    singletons = {}

    def __call__(cls, *args, **kwargs):
        if cls in Meta_2.singletons:
            # we return the only instance and skip a call to __new__()
            # and __init__()
            print ("{} singleton returning from Meta_2.__call__(), "
                   "skipping creation of new instance.".format(cls))
            return Meta_2.singletons[cls]

        # else if the singleton isn't present we proceed as usual
        print "Meta_2.__call__() before creating an instance."
        instance = super(Meta_2, cls).__call__(*args, **kwargs)
        Meta_2.singletons[cls] = instance
        print "Meta_2.__call__() returning new instance."
        return instance

class Class_2(object):

    __metaclass__ = Meta_2

    def __new__(cls, *args, **kwargs):
        print "Class_2.__new__() before creating instance."
        instance = super(Class_2, cls).__new__(cls)
        print "Class_2.__new__() returning instance."
        return instance

    def __init__(self, *args, **kwargs):
        print "entering Class_2.__init__() for initialization."
        super(Class_2, self).__init__()
        print "exiting Class_2.__init__()."

Beobachten wir, was passiert, wenn wiederholt versucht wird, ein Objekt vom Typ zu erstellen Class_2

a = Class_2()
# Meta_2.__call__() before creating an instance.
# Class_2.__new__() before creating instance.
# Class_2.__new__() returning instance.
# entering Class_2.__init__() for initialization.
# exiting Class_2.__init__().
# Meta_2.__call__() returning new instance.

b = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.

c = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.

a is b is c # True

Dies ist eine gute Ergänzung zu der zuvor positiv bewerteten "akzeptierten Antwort". Es enthält Beispiele für Zwischencodierer zum Kauen.
Rich Lysakowski PhD

56

Eine Metaklasse ist eine Klasse, die angibt, wie (einige) andere Klassen erstellt werden sollen.

Dies ist ein Fall, in dem ich Metaklasse als Lösung für mein Problem sah: Ich hatte ein wirklich kompliziertes Problem, das wahrscheinlich anders hätte gelöst werden können, aber ich entschied mich, es mit einer Metaklasse zu lösen. Aufgrund der Komplexität ist es eines der wenigen Module, die ich geschrieben habe, bei denen die Kommentare im Modul die Menge des geschriebenen Codes überschreiten. Hier ist es...

#!/usr/bin/env python

# Copyright (C) 2013-2014 Craig Phillips.  All rights reserved.

# This requires some explaining.  The point of this metaclass excercise is to
# create a static abstract class that is in one way or another, dormant until
# queried.  I experimented with creating a singlton on import, but that did
# not quite behave how I wanted it to.  See now here, we are creating a class
# called GsyncOptions, that on import, will do nothing except state that its
# class creator is GsyncOptionsType.  This means, docopt doesn't parse any
# of the help document, nor does it start processing command line options.
# So importing this module becomes really efficient.  The complicated bit
# comes from requiring the GsyncOptions class to be static.  By that, I mean
# any property on it, may or may not exist, since they are not statically
# defined; so I can't simply just define the class with a whole bunch of
# properties that are @property @staticmethods.
#
# So here's how it works:
#
# Executing 'from libgsync.options import GsyncOptions' does nothing more
# than load up this module, define the Type and the Class and import them
# into the callers namespace.  Simple.
#
# Invoking 'GsyncOptions.debug' for the first time, or any other property
# causes the __metaclass__ __getattr__ method to be called, since the class
# is not instantiated as a class instance yet.  The __getattr__ method on
# the type then initialises the class (GsyncOptions) via the __initialiseClass
# method.  This is the first and only time the class will actually have its
# dictionary statically populated.  The docopt module is invoked to parse the
# usage document and generate command line options from it.  These are then
# paired with their defaults and what's in sys.argv.  After all that, we
# setup some dynamic properties that could not be defined by their name in
# the usage, before everything is then transplanted onto the actual class
# object (or static class GsyncOptions).
#
# Another piece of magic, is to allow command line options to be set in
# in their native form and be translated into argparse style properties.
#
# Finally, the GsyncListOptions class is actually where the options are
# stored.  This only acts as a mechanism for storing options as lists, to
# allow aggregation of duplicate options or options that can be specified
# multiple times.  The __getattr__ call hides this by default, returning the
# last item in a property's list.  However, if the entire list is required,
# calling the 'list()' method on the GsyncOptions class, returns a reference
# to the GsyncListOptions class, which contains all of the same properties
# but as lists and without the duplication of having them as both lists and
# static singlton values.
#
# So this actually means that GsyncOptions is actually a static proxy class...
#
# ...And all this is neatly hidden within a closure for safe keeping.
def GetGsyncOptionsType():
    class GsyncListOptions(object):
        __initialised = False

    class GsyncOptionsType(type):
        def __initialiseClass(cls):
            if GsyncListOptions._GsyncListOptions__initialised: return

            from docopt import docopt
            from libgsync.options import doc
            from libgsync import __version__

            options = docopt(
                doc.__doc__ % __version__,
                version = __version__,
                options_first = True
            )

            paths = options.pop('<path>', None)
            setattr(cls, "destination_path", paths.pop() if paths else None)
            setattr(cls, "source_paths", paths)
            setattr(cls, "options", options)

            for k, v in options.iteritems():
                setattr(cls, k, v)

            GsyncListOptions._GsyncListOptions__initialised = True

        def list(cls):
            return GsyncListOptions

        def __getattr__(cls, name):
            cls.__initialiseClass()
            return getattr(GsyncListOptions, name)[-1]

        def __setattr__(cls, name, value):
            # Substitut option names: --an-option-name for an_option_name
            import re
            name = re.sub(r'^__', "", re.sub(r'-', "_", name))
            listvalue = []

            # Ensure value is converted to a list type for GsyncListOptions
            if isinstance(value, list):
                if value:
                    listvalue = [] + value
                else:
                    listvalue = [ None ]
            else:
                listvalue = [ value ]

            type.__setattr__(GsyncListOptions, name, listvalue)

    # Cleanup this module to prevent tinkering.
    import sys
    module = sys.modules[__name__]
    del module.__dict__['GetGsyncOptionsType']

    return GsyncOptionsType

# Our singlton abstract proxy class.
class GsyncOptions(object):
    __metaclass__ = GetGsyncOptionsType()

43

Die tl; dr-Version

Mit der type(obj)Funktion erhalten Sie den Typ eines Objekts.

Das type()einer Klasse ist ihre Metaklasse .

So verwenden Sie eine Metaklasse:

class Foo(object):
    __metaclass__ = MyMetaClass

typeist eine eigene Metaklasse. Die Klasse einer Klasse ist eine Metaklasse - der Hauptteil einer Klasse sind die Argumente, die an die Metaklasse übergeben werden, die zum Erstellen der Klasse verwendet wird.

Hier erfahren Sie, wie Sie mithilfe von Metaklassen die Klassenkonstruktion anpassen.


42

typeist eigentlich eine metaclass- eine Klasse, die andere Klassen erstellt. Die meisten metaclasssind die Unterklassen von type. Der metaclassempfängt die newKlasse als erstes Argument und bietet Zugriff auf das Klassenobjekt mit den folgenden Details:

>>> class MetaClass(type):
...     def __init__(cls, name, bases, attrs):
...         print ('class name: %s' %name )
...         print ('Defining class %s' %cls)
...         print('Bases %s: ' %bases)
...         print('Attributes')
...         for (name, value) in attrs.items():
...             print ('%s :%r' %(name, value))
... 

>>> class NewClass(object, metaclass=MetaClass):
...    get_choch='dairy'
... 
class name: NewClass
Bases <class 'object'>: 
Defining class <class 'NewClass'>
get_choch :'dairy'
__module__ :'builtins'
__qualname__ :'NewClass'

Note:

Beachten Sie, dass die Klasse zu keinem Zeitpunkt instanziiert wurde. Der einfache Vorgang des Erstellens der Klasse löste die Ausführung des aus metaclass.


27

Python-Klassen sind selbst Objekte - wie zum Beispiel - ihrer Meta-Klasse.

Die Standard-Metaklasse, die angewendet wird, wenn Sie Klassen wie folgt bestimmen:

class foo:
    ...

Meta-Klassen werden verwendet, um eine Regel auf eine ganze Reihe von Klassen anzuwenden. Angenommen, Sie erstellen ein ORM für den Zugriff auf eine Datenbank und möchten, dass Datensätze aus jeder Tabelle einer Klasse angehören, die dieser Tabelle zugeordnet ist (basierend auf Feldern, Geschäftsregeln usw.), eine mögliche Verwendung der Metaklasse ist beispielsweise die Verbindungspoollogik, die von allen Datensatzklassen aus allen Tabellen gemeinsam genutzt wird. Eine andere Verwendung ist die Logik zur Unterstützung von Fremdschlüsseln, die mehrere Klassen von Datensätzen umfasst.

Wenn Sie eine Metaklasse definieren, geben Sie einen Unterklassentyp ein und können die folgenden magischen Methoden überschreiben, um Ihre Logik einzufügen.

class somemeta(type):
    __new__(mcs, name, bases, clsdict):
      """
  mcs: is the base metaclass, in this case type.
  name: name of the new class, as provided by the user.
  bases: tuple of base classes 
  clsdict: a dictionary containing all methods and attributes defined on class

  you must return a class object by invoking the __new__ constructor on the base metaclass. 
 ie: 
    return type.__call__(mcs, name, bases, clsdict).

  in the following case:

  class foo(baseclass):
        __metaclass__ = somemeta

  an_attr = 12

  def bar(self):
      ...

  @classmethod
  def foo(cls):
      ...

      arguments would be : ( somemeta, "foo", (baseclass, baseofbase,..., object), {"an_attr":12, "bar": <function>, "foo": <bound class method>}

      you can modify any of these values before passing on to type
      """
      return type.__call__(mcs, name, bases, clsdict)


    def __init__(self, name, bases, clsdict):
      """ 
      called after type has been created. unlike in standard classes, __init__ method cannot modify the instance (cls) - and should be used for class validaton.
      """
      pass


    def __prepare__():
        """
        returns a dict or something that can be used as a namespace.
        the type will then attach methods and attributes from class definition to it.

        call order :

        somemeta.__new__ ->  type.__new__ -> type.__init__ -> somemeta.__init__ 
        """
        return dict()

    def mymethod(cls):
        """ works like a classmethod, but for class objects. Also, my method will not be visible to instances of cls.
        """
        pass

Jedenfalls sind diese beiden die am häufigsten verwendeten Haken. Metaclassing ist leistungsstark, und oben finden Sie bei weitem keine vollständige Liste der Verwendungszwecke für Metaclassing.


21

Die Funktion type () kann den Typ eines Objekts zurückgeben oder einen neuen Typ erstellen.

Zum Beispiel können wir eine Hi-Klasse mit der Funktion type () erstellen und müssen diese Methode nicht mit der Klasse Hi (Objekt) verwenden:

def func(self, name='mike'):
    print('Hi, %s.' % name)

Hi = type('Hi', (object,), dict(hi=func))
h = Hi()
h.hi()
Hi, mike.

type(Hi)
type

type(h)
__main__.Hi

Zusätzlich zur Verwendung von type () zum dynamischen Erstellen von Klassen können Sie das Erstellungsverhalten von Klassen steuern und Metaklassen verwenden.

Gemäß dem Python-Objektmodell ist die Klasse das Objekt, daher muss die Klasse eine Instanz einer anderen bestimmten Klasse sein. Standardmäßig ist eine Python-Klasse eine Instanz der Typklasse. Das heißt, Typ ist Metaklasse der meisten integrierten Klassen und Metaklasse benutzerdefinierter Klassen.

class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

class CustomList(list, metaclass=ListMetaclass):
    pass

lst = CustomList()
lst.add('custom_list_1')
lst.add('custom_list_2')

lst
['custom_list_1', 'custom_list_2']

Magic wird wirksam, wenn wir Schlüsselwortargumente in der Metaklasse übergeben haben. Es gibt den Python-Interpreter an, der die CustomList über ListMetaclass erstellen soll. new (), an dieser Stelle können wir beispielsweise die Klassendefinition ändern, eine neue Methode hinzufügen und dann die überarbeitete Definition zurückgeben.


11

Zusätzlich zu den veröffentlichten Antworten kann ich sagen, dass a metaclassdas Verhalten für eine Klasse definiert. So können Sie Ihre Metaklasse explizit festlegen. Immer wenn Python ein Schlüsselwort erhält class, beginnt es nach dem zu suchen metaclass. Wenn es nicht gefunden wird, wird der Standard-Metaklassentyp verwendet, um das Objekt der Klasse zu erstellen. Mit dem __metaclass__Attribut können Sie metaclassIhre Klasse festlegen :

class MyClass:
   __metaclass__ = type
   # write here other method
   # write here one more method

print(MyClass.__metaclass__)

Die Ausgabe wird folgendermaßen erzeugt:

class 'type'

Und natürlich können Sie Ihre eigenen erstellen metaclass, um das Verhalten jeder Klasse zu definieren, die mit Ihrer Klasse erstellt wird.

Dazu muss Ihre Standardtypklasse metaclassgeerbt werden, da dies die Hauptklasse ist metaclass:

class MyMetaClass(type):
   __metaclass__ = type
   # you can write here any behaviour you want

class MyTestClass:
   __metaclass__ = MyMetaClass

Obj = MyTestClass()
print(Obj.__metaclass__)
print(MyMetaClass.__metaclass__)

Die Ausgabe wird sein:

class '__main__.MyMetaClass'
class 'type'

4

Bei der objektorientierten Programmierung ist eine Metaklasse eine Klasse, deren Instanzen Klassen sind. So wie eine gewöhnliche Klasse das Verhalten bestimmter Objekte definiert, definiert eine Metaklasse das Verhalten bestimmter Klassen und ihrer Instanzen. Der Begriff Metaklasse bedeutet einfach etwas, das zum Erstellen von Klassen verwendet wird. Mit anderen Worten, es ist die Klasse einer Klasse. Die Metaklasse wird verwendet, um die Klasse so zu erstellen, dass eine Klasse eine Instanz einer Metaklasse ist, so wie das Objekt eine Instanz einer Klasse ist. In Python werden Klassen auch als Objekte betrachtet.


Anstatt buchstäbliche Definitionen zu geben, wäre es besser gewesen, wenn Sie einige Beispiele hinzugefügt hätten. Die erste Zeile Ihrer Antwort scheint aus dem Wikipedia-Eintrag von Metaclasses kopiert worden zu sein.
Wahrhaftigkeit

@verisimilitude Ich lerne auch, können Sie mir helfen, diese Antwort zu verbessern, indem Sie einige praktische Beispiele aus Ihrer Erfahrung liefern?
Venu Gopal Tewari

2

Hier ist ein weiteres Beispiel dafür, wofür es verwendet werden kann:

  • Mit können Sie metaclassdie Funktion der Instanz (der Klasse) ändern.
class MetaMemberControl(type):
    __slots__ = ()

    @classmethod
    def __prepare__(mcs, f_cls_name, f_cls_parents,  # f_cls means: future class
                    meta_args=None, meta_options=None):  # meta_args and meta_options is not necessarily needed, just so you know.
        f_cls_attr = dict()
        if not "do something or if you want to define your cool stuff of dict...":
            return dict(make_your_special_dict=None)
        else:
            return f_cls_attr

    def __new__(mcs, f_cls_name, f_cls_parents, f_cls_attr,
                meta_args=None, meta_options=None):

        original_getattr = f_cls_attr.get('__getattribute__')
        original_setattr = f_cls_attr.get('__setattr__')

        def init_getattr(self, item):
            if not item.startswith('_'):  # you can set break points at here
                alias_name = '_' + item
                if alias_name in f_cls_attr['__slots__']:
                    item = alias_name
            if original_getattr is not None:
                return original_getattr(self, item)
            else:
                return super(eval(f_cls_name), self).__getattribute__(item)

        def init_setattr(self, key, value):
            if not key.startswith('_') and ('_' + key) in f_cls_attr['__slots__']:
                raise AttributeError(f"you can't modify private members:_{key}")
            if original_setattr is not None:
                original_setattr(self, key, value)
            else:
                super(eval(f_cls_name), self).__setattr__(key, value)

        f_cls_attr['__getattribute__'] = init_getattr
        f_cls_attr['__setattr__'] = init_setattr

        cls = super().__new__(mcs, f_cls_name, f_cls_parents, f_cls_attr)
        return cls


class Human(metaclass=MetaMemberControl):
    __slots__ = ('_age', '_name')

    def __init__(self, name, age):
        self._name = name
        self._age = age

    def __getattribute__(self, item):
        """
        is just for IDE recognize.
        """
        return super().__getattribute__(item)

    """ with MetaMemberControl then you don't have to write as following
    @property
    def name(self):
        return self._name

    @property
    def age(self):
        return self._age
    """


def test_demo():
    human = Human('Carson', 27)
    # human.age = 18  # you can't modify private members:_age  <-- this is defined by yourself.
    # human.k = 18  # 'Human' object has no attribute 'k'  <-- system error.
    age1 = human._age  # It's OK, although the IDE will show some warnings. (Access to a protected member _age of a class)

    age2 = human.age  # It's OK! see below:
    """
    if you do not define `__getattribute__` at the class of Human,
    the IDE will show you: Unresolved attribute reference 'age' for class 'Human'
    but it's ok on running since the MetaMemberControl will help you.
    """


if __name__ == '__main__':
    test_demo()

Das metaclassist mächtig, es gibt viele Dinge (wie Affenmagie), die Sie damit machen können, aber seien Sie vorsichtig, dies ist möglicherweise nur Ihnen bekannt.


2

Eine Klasse in Python ist ein Objekt und wie jedes andere Objekt eine Instanz von "etwas". Dieses "Etwas" wird als Metaklasse bezeichnet. Diese Metaklasse ist ein spezieller Klassentyp, der Objekte anderer Klassen erstellt. Daher ist die Metaklasse für die Erstellung neuer Klassen verantwortlich. Auf diese Weise kann der Programmierer die Art und Weise anpassen, wie Klassen generiert werden.

Um eine Metaklasse zu erstellen, werden normalerweise die Methoden new () und init () überschrieben . new () kann überschrieben werden, um die Art und Weise zu ändern, wie Objekte erstellt werden, während init () überschrieben werden kann, um die Art und Weise der Initialisierung des Objekts zu ändern. Metaklasse kann auf verschiedene Arten erstellt werden. Eine Möglichkeit besteht darin, die Funktion type () zu verwenden. Die Funktion type () erstellt beim Aufruf mit 3 Parametern eine Metaklasse. Die Parameter sind: -

  1. Klassenname
  2. Tupel mit Basisklassen, die von der Klasse geerbt wurden
  3. Ein Wörterbuch mit allen Klassenmethoden und Klassenvariablen

Eine andere Möglichkeit zum Erstellen einer Metaklasse besteht aus dem Schlüsselwort "Metaklasse". Definieren Sie die Metaklasse als einfache Klasse. Übergeben Sie in den Parametern der geerbten Klasse metaclass = metaclass_name

Metaklasse kann speziell in den folgenden Situationen verwendet werden: -

  1. wenn ein bestimmter Effekt auf alle Unterklassen angewendet werden muss
  2. Ein automatischer Klassenwechsel (bei der Erstellung) ist erforderlich
  3. Von API-Entwicklern

2

Beachten Sie, dass in Python 3.6 eine neue Dunder-Methode __init_subclass__(cls, **kwargs)eingeführt wurde, um viele gängige Anwendungsfälle für Metaklassen zu ersetzen. Is wird aufgerufen, wenn eine Unterklasse der definierenden Klasse erstellt wird. Siehe Python-Dokumente .


-3

Metaklasse ist eine Art Klasse, die definiert, wie sich die Klasse verhält, oder wir können sagen, dass eine Klasse selbst eine Instanz einer Metaklasse ist.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.