7371 Stimmen

Was sind Metaklassen in Python?

Was sind Metaklassen in Python und wofür werden sie verwendet?

9019voto

e-satis Punkte 547539

Klassen als Objekte

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

In den meisten Sprachen sind Klassen nur Codestücke, die beschreiben, wie man ein Objekt erzeugt. Das ist auch in Python irgendwie wahr:

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

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

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

Ja, Objekte.

Sobald Sie das Schlüsselwort class aus, führt Python es aus und erstellt eine objeto . Die Anweisung

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

erzeugt im Speicher ein Objekt mit dem Namen ObjectCreator .

Dieses Objekt (die Klasse) ist selbst in der Lage, Objekte (die Instanzen) zu erzeugen, und deshalb ist es eine Klasse .

Aber trotzdem ist es ein Objekt, und deshalb:

  • können Sie sie einer Variablen zuweisen
  • Sie können es kopieren
  • können Sie ihm Attribute hinzufügen
  • können Sie ihn als Funktionsparameter übergeben

z.B.:

>>> print(ObjectCreator) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
...       print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

Klassen dynamisch erstellen

Da es sich bei Klassen um Objekte handelt, können Sie sie wie jedes andere Objekt spontan erstellen.

Zunächst können Sie eine Klasse in einer Funktion erstellen, indem Sie class :

>>> def choose_class(name):
...     if name == 'foo':
...         class Foo(object):
...             pass
...         return Foo # return the class, not an instance
...     else:
...         class Bar(object):
...             pass
...         return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>

Aber es ist nicht so dynamisch, da man die ganze Klasse immer noch selbst schreiben muss.

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

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

Erinnern Sie sich an die Funktion type ? Die gute alte Funktion, mit der Sie wissen, welchen Typ ein Objekt ist:

>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>

Nun, type hat eine ganz andere Fähigkeit, es kann auch Klassen im laufenden Betrieb erstellen. type kann die Beschreibung einer Klasse als Parameter annehmen, und eine Klasse zurückgeben.

(Ich weiß, es ist albern, dass ein und dieselbe Funktion zwei völlig unterschiedliche Verwendungszwecke haben kann, je nachdem, welche Parameter Sie ihr übergeben. Das ist ein Problem der Abwärtskompatibilität Kompatibilität in Python)

type funktioniert folgendermaßen:

type(name, bases, attrs)

Wo:

  • name Name der Klasse: Name der Klasse
  • bases Tupel der Elternklasse (für Vererbung, kann leer sein)
  • attrs Wörterbuch mit Namen und Werten der Attribute

z.B.:

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

kann auf diese Weise manuell erstellt werden:

>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>

Sie werden feststellen, dass wir MyShinyClass als Name der Klasse und als Variable, die die Klassenreferenz enthält. Sie können unterschiedlich sein, aber es gibt keinen Grund, die Dinge zu verkomplizieren.

type akzeptiert ein Wörterbuch, um die Attribute der Klasse zu definieren. So:

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

Kann übersetzt werden mit:

>>> Foo = type('Foo', (), {'bar':True})

Und als normale Klasse verwendet:

>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True

Und natürlich kann man sie auch vererben, also:

>>>   class FooChild(Foo):
...         pass

wäre:

>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True

Schließlich werden Sie Ihrer Klasse Methoden hinzufügen wollen. Definieren Sie einfach eine Funktion mit der richtigen Signatur und weisen Sie sie als Attribut zu.

>>> def echo_bar(self):
...       print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True

Und Sie können noch mehr Methoden hinzufügen, nachdem Sie die Klasse dynamisch erstellt haben, genauso wie Sie Methoden zu einem normal erstellten Klassenobjekt hinzufügen.

>>> def echo_bar_more(self):
...       print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True

Sie sehen, worauf wir hinauswollen: In Python sind Klassen Objekte, und Sie können eine Klasse spontan und dynamisch erstellen.

Das ist es, was Python tut, wenn Sie das Schlüsselwort class und zwar unter Verwendung einer Metaklasse.

Was sind Metaklassen (endlich)

Metaklassen sind das "Material", aus dem Klassen entstehen.

Sie definieren Klassen, um Objekte zu erstellen, richtig?

Aber wir haben gelernt, dass Python-Klassen Objekte sind.

Nun, diese Objekte werden durch Metaklassen erzeugt. Sie sind die Klassen der Klassen, man kann sie sich so vorstellen:

MyClass = MetaClass()
my_object = MyClass()

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

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

Das liegt daran, dass die Funktion type ist in der Tat eine Metaklasse. type ist die Metaklasse, mit der Python alle Klassen im Hintergrund erstellt.

Jetzt fragen Sie sich: "Warum zum Teufel ist es klein geschrieben und nicht Type ?"

Nun, ich denke, es ist eine Frage der Kohärenz mit str , die Klasse, die die Strings-Objekte erstellt, und int die Klasse, die Integer-Objekte erzeugt. type ist nur die Klasse, die Klassenobjekte erzeugt.

Sie sehen das, indem Sie die __class__ Attribut.

Alles, und ich meine wirklich alles, ist in Python ein Objekt. Das schließt Ganzzahlen, Zeichenketten, Funktionen und Klassen. Sie alle sind Objekte. Und sie alle wurden wurden aus einer Klasse erstellt:

>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>

Nun, was ist die __class__ eines beliebigen __class__ ?

>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>

Eine Metaklasse ist also nur das Material, das Klassenobjekte erzeugt.

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

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

Le site __metaclass__ Attribut

In Python 2 können Sie eine __metaclass__ Attribut, wenn Sie eine Klasse schreiben (siehe nächster Abschnitt für die Python 3-Syntax):

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

Wenn Sie dies tun, wird Python die Metaklasse verwenden, um die Klasse Foo .

Vorsicht, das ist knifflig.

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

Python sucht nach __metaclass__ in der Klassendefinition. Wenn er sie findet, wird er sie zur Erstellung der Objektklasse verwenden Foo . Ist dies nicht der Fall, verwendet es type um die Klasse zu erstellen.

Lesen Sie das mehrere Male.

Wenn Sie das tun:

class Foo(Bar):
    pass

Python macht Folgendes:

Gibt es eine __metaclass__ Attribut in Foo ?

Wenn ja, erstellen Sie im Speicher ein Klassenobjekt (ich sagte Klassenobjekt, bleiben Sie bei mir) mit dem Namen Foo indem wir das verwenden, was in __metaclass__ .

Wenn Python nicht finden kann __metaclass__ sucht es nach einer __metaclass__ auf der MODULE-Ebene und versuchen Sie, dasselbe zu tun (aber nur für Klassen, die nichts erben, im Grunde genommen Klassen alten Stils).

Wenn es dann nichts finden kann __metaclass__ überhaupt nicht, wird es die Bar (der ersten übergeordneten) Metaklasse (die die Standard-Metaklasse sein kann) type ), um das Klassenobjekt zu erstellen.

Achten Sie dabei darauf, dass die __metaclass__ wird nicht vererbt, die Metaklasse der übergeordneten Klasse ( Bar.__class__ ) sein wird. Wenn Bar verwendet eine __metaclass__ Attribut, das die Bar con type() (und nicht type.__new__() ), werden die Unterklassen dieses Verhalten nicht erben.

Die große Frage ist nun, was kann man in __metaclass__ ?

Die Antwort ist etwas, das eine Klasse schaffen kann.

Und was kann eine Klasse schaffen? type oder irgendetwas, das es untergliedert oder verwendet.

Metaklassen in Python 3

Die Syntax zum Setzen der Metaklasse hat sich in Python 3 geändert:

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

d.h. die __metaclass__ Attribut wird nicht mehr verwendet, stattdessen wird ein Schlüsselwortargument in der Liste der Basisklassen verwendet.

Das Verhalten von Metaklassen bleibt jedoch weitgehend identisch .

Eine Sache, die in Python 3 zu den Metaklassen hinzugekommen ist, ist, dass man auch Attribute als Schlüsselwort-Argumente an eine Metaklasse übergeben kann, etwa so:

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

Lesen Sie im folgenden Abschnitt, wie Python dies handhabt.

Benutzerdefinierte Metaklassen

Der Hauptzweck einer Metaklasse besteht darin, die Klasse automatisch zu ändern, wenn sie erstellt wird.

Normalerweise tun Sie dies für APIs, wo Sie Klassen erstellen wollen, die der aktuellen Kontext passen.

Stellen Sie sich ein dummes Beispiel vor, in dem Sie entscheiden, dass alle Klassen in Ihrem Modul ihre Attribute in Großbuchstaben geschrieben haben sollen. Es gibt mehrere Möglichkeiten, dies dies zu tun, aber eine Möglichkeit ist, die __metaclass__ auf der Ebene der Module.

Auf diese Weise werden alle Klassen dieses Moduls unter Verwendung dieser Metaklasse erstellt, und wir müssen der Metaklasse nur sagen, dass sie alle Attribute in Großbuchstaben umwandeln soll.

Zum Glück, __metaclass__ kann eigentlich jedes Callable sein, es muss keine formale Klasse sein (ich weiß, etwas mit "Klasse" im Namen muss keine Klasse sein eine Klasse sein, stellen Sie sich vor... aber es ist hilfreich).

Wir beginnen also mit einem einfachen Beispiel, indem wir eine Funktion verwenden.

# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attrs):
    """
      Return a class object, with the list of its attribute turned
      into uppercase.
    """
    # pick up any attribute that doesn't start with '__' and uppercase it
    uppercase_attrs = {
        attr if attr.startswith("__") else attr.upper(): v
        for attr, v in future_class_attrs.items()
    }

    # let `type` do the class creation
    return type(future_class_name, future_class_parents, uppercase_attrs)

__metaclass__ = upper_attr # this will affect all classes in the module

class Foo(): # global __metaclass__ won't work with "object" though
    # but we can define __metaclass__ here instead to affect only this class
    # and this will work with "object" children
    bar = 'bip'

Schauen wir nach:

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

Nun wollen wir genau dasselbe tun, aber eine echte Klasse als Metaklasse verwenden:

# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
    # __new__ is the method called before __init__
    # it's the method that creates the object and returns it
    # while __init__ just initializes the object passed as parameter
    # you rarely use __new__, except when you want to control how the object
    # is created.
    # here the created object is the class, and we want to customize it
    # so we override __new__
    # you can do some stuff in __init__ too if you wish
    # some advanced use involves overriding __call__ as well, but we won't
    # see this
    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in future_class_attrs.items()
        }
        return type(future_class_name, future_class_parents, uppercase_attrs)

Lassen Sie uns die obigen Zeilen neu schreiben, aber mit kürzeren und realistischeren Variablennamen, da wir jetzt wissen, was sie bedeuten:

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return type(clsname, bases, uppercase_attrs)

Sie haben vielleicht das zusätzliche Argument bemerkt cls . Es ist nichts Besonderes an sich: __new__ erhält immer die Klasse, in der sie definiert ist, als ersten Parameter. Genauso wie Sie self für gewöhnliche Methoden, die die Instanz als ersten Parameter erhalten, oder die definierende Klasse für Klassenmethoden.

Aber das ist kein richtiges OOP. Wir rufen type direkt und wir überschreiben oder rufen nicht die übergeordnete __new__ . Das sollten wir stattdessen tun:

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return type.__new__(cls, clsname, bases, uppercase_attrs)

Wir können es noch sauberer machen, indem wir super was die Vererbung erleichtern wird (denn ja, man kann Metaklassen haben, die von Metaklassen erben, die von Typ erben):

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return super(UpperAttrMetaclass, cls).__new__(
            cls, clsname, bases, uppercase_attrs)

Oh, und in Python 3, wenn Sie diesen Aufruf mit Schlüsselwort-Argumenten, wie dies tun:

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

Es wird in der Metaklasse so übersetzt, dass es verwendet werden kann:

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

Das war's. Mehr gibt es über Metaklassen eigentlich nicht zu sagen.

Der Grund für die Komplexität des Codes, der Metaklassen verwendet, liegt nicht an der Metaklassen, sondern daran, dass man Metaklassen in der Regel verwendet, um verdrehte Dinge zu tun Introspektion, Manipulation von Vererbung, Variablen wie __dict__ , usw.

In der Tat sind Metaklassen besonders nützlich, um schwarze Magie zu betreiben, und daher komplizierte Dinge. Aber an sich sind sie einfach:

  • eine Klassenbildung abfangen
  • die Klasse ändern
  • die geänderte Klasse zurückgeben

Warum sollten Sie Metaklassen anstelle von Funktionen verwenden?

Seit __metaclass__ jeden Callable akzeptieren kann, warum sollten Sie eine Klasse verwenden verwenden, da sie offensichtlich komplizierter ist?

Hierfür gibt es mehrere Gründe:

  • Die Absicht ist klar. Wenn Sie lesen UpperAttrMetaclass(type) wissen Sie was folgen wird
  • Sie können OOP verwenden. Metaklassen können von Metaklassen erben und übergeordnete Methoden außer Kraft setzen. Metaklassen können sogar Metaklassen verwenden.
  • Unterklassen einer Klasse sind Instanzen ihrer Metaklasse, wenn Sie eine Metaklassen-Klasse angegeben haben, aber nicht mit einer Metaklassen-Funktion.
  • Sie können Ihren Code besser strukturieren. Sie verwenden Metaklassen nie für etwas so Triviales wie das obige Beispiel. Normalerweise ist es für etwas Kompliziertes. Die Möglichkeit, mehrere Methoden zu erstellen und sie in einer Klasse zu gruppieren, ist sehr nützlich, um den Code leichter lesbar zu machen.
  • Sie können einhaken __new__ , __init__ y __call__ . Das erlaubt Ihnen, verschiedene Dinge zu tun, auch wenn Sie normalerweise alles in einer Woche erledigen können. __new__ , Manche Menschen fühlen sich einfach wohler, wenn sie __init__ .
  • Diese heißen Metaklassen, verdammt noch mal! Das muss etwas bedeuten!

Warum sollten Sie Metaklassen verwenden?

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

Nun, normalerweise nicht:

Metaklassen sind tiefere Magie, die 99 % der Benutzer sollten sich keine Gedanken darüber machen. Wenn Sie sich fragen, ob Sie sie brauchen, brauchen Sie sie nicht (die Leute, die sie tatsächlich brauchen, wissen mit Gewissheit, dass dass sie sie brauchen, und sie brauchen keine Erklärung, warum).

Python-Guru Tim Peters

Der Hauptanwendungsfall für eine Metaklasse ist die Erstellung einer API. Ein typisches Beispiel hierfür ist der Django ORM. Es erlaubt Ihnen, etwas wie dieses zu definieren:

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

Aber wenn Sie das tun:

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

Es wird nicht ein IntegerField Gegenstand. Es gibt ein int und kann sie sogar direkt aus der Datenbank übernehmen.

Dies ist möglich, weil models.Model definiert __metaclass__ und wird ein Zauber angewendet, der die Person die Sie gerade mit einfachen Aussagen definiert haben in eine komplexe Verknüpfung mit einem Datenbankfeld verwandeln.

Django lässt etwas Komplexes einfach aussehen, indem es eine einfache API zur Verfügung stellt und die Verwendung von Metaklassen, die den Code dieser API nachbilden, um die eigentliche Aufgabe hinter den Kulissen.

Das letzte Wort

Zunächst wissen Sie, dass Klassen Objekte sind, die Instanzen erzeugen können.

Nun, eigentlich sind Klassen selbst Instanzen. Von Metaklassen.

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

In Python ist alles ein Objekt, und alle sind entweder Instanzen von Klassen oder Instanzen von Metaklassen.

Außer bei type .

type ist eigentlich eine eigene Metaklasse. Dies ist nicht etwas, das Sie in reinem Python reproduzieren und wird durch ein wenig Schummeln auf der Implementierungsebene Ebene.

Zweitens: Metaklassen sind kompliziert. Sie wollen sie vielleicht nicht für sehr einfache Klassenänderungen verwenden. Sie können Klassen mithilfe von zwei verschiedenen Techniken ändern:

In 99 % der Fälle, in denen Sie eine Klassenänderung benötigen, sind Sie mit diesen besser dran.

Aber in 98 % der Fälle brauchen Sie überhaupt keine Klassenänderung.

78 Stimmen

Es scheint, dass in Django models.Model er verwendet nicht __metaclass__ sondern vielmehr class Model(metaclass=ModelBase): auf eine ModelBase Klasse, die dann die oben erwähnte Metaklassenzauberei durchführt. Toller Beitrag! Hier ist der Django-Quelltext: github.com/django/django/blob/master/django/db/models/

0 Stimmen

Was, wenn anders anzeigen Metaklasse sowohl in der Basisklasse als auch in der abgeleiteten Klasse?

36 Stimmen

<<Hier ist darauf zu achten, dass die __metaclass__ wird nicht vererbt, die Metaklasse der übergeordneten Klasse ( Bar.__class__ ) sein wird. Wenn Bar verwendet eine __metaclass__ Attribut, das die Bar mit type() (und nicht type.__new__() ), werden die Unterklassen dieses Verhalten nicht erben.>> -- Könnten Sie/jemand bitte diese Passage etwas genauer erklären?

3429voto

Thomas Wouters Punkte 124421

Eine Metaklasse ist die Klasse einer Klasse. Eine Klasse definiert, wie sich eine Instanz der Klasse (d. h. 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), ist es besser, sie zu einer eigenen Klasse zu machen. type ist die übliche Metaklasse in Python. type ist selbst eine Klasse, die einen eigenen Typ darstellt. Sie werden nicht in der Lage sein, etwas zu erstellen wie type rein in Python, aber Python schummelt ein wenig. Um Ihre eigene Metaklasse in Python zu erstellen, brauchen Sie eigentlich nur eine Unterklasse type .

Eine Metaklasse wird in der Regel als Klassenfabrik verwendet. Wenn Sie ein Objekt durch den Aufruf der Klasse erzeugen, erzeugt Python eine neue Klasse (wenn es die Anweisung "class" ausführt), indem es die Metaklasse aufruft. Kombiniert mit der normalen __init__ y __new__ Methoden, Metaklassen ermöglichen es Ihnen daher, bei der Erstellung einer Klasse "zusätzliche Dinge" zu tun, wie z. B. die Registrierung der neuen Klasse bei einer Registrierung oder das Ersetzen der Klasse durch etwas völlig anderes.

Wenn die class Anweisung ausgeführt wird, führt Python zuerst den Körper der class Anweisung wie ein normaler Code-Block. Der daraus resultierende Namespace (ein dict) enthält die Attribute der zu erstellenden Klasse. Die Metaklasse wird bestimmt, indem man sich die Basisklassen der zukünftigen Klasse ansieht (Metaklassen werden vererbt), die __metaclass__ Attribut der zukünftigen Klasse (falls vorhanden) oder das __metaclass__ globale Variable. Die Metaklasse wird dann mit dem Namen, den Basen und den Attributen der Klasse aufgerufen, um sie zu instanziieren.

Die Metaklassen definieren jedoch tatsächlich die Typ einer Klasse, nicht nur eine Fabrik für sie, so dass man viel mehr mit ihnen machen kann. Sie können zum Beispiel normale Methoden für die Metaklasse definieren. Diese Metaklassen-Methoden sind wie Klassenmethoden, da sie in der Klasse ohne Instanz aufgerufen werden können, aber sie sind auch nicht wie Klassenmethoden, da sie nicht in einer Instanz der Klasse aufgerufen werden können. type.__subclasses__() ist ein Beispiel für eine Methode in der type Metaklasse. Sie können auch die normalen "magischen" Methoden definieren, wie __add__ , __iter__ y __getattr__ , um das Verhalten der Klasse zu implementieren oder zu ändern.

Hier ist ein zusammenfassendes Beispiel für die einzelnen Elemente:

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__

20 Stimmen

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

37 Stimmen

Ppperry meinte er offensichtlich, dass man den Typ nicht neu erstellen kann, ohne den Typ selbst als Metaklasse zu verwenden. Das kann man durchaus so sagen.

7 Stimmen

Sollte unregister() nicht von einer Instanz der Klasse Example aufgerufen werden?

491voto

Jerub Punkte 40038

Beachten Sie, diese Antwort ist für Python 2.x, wie es im Jahr 2008 geschrieben wurde, Metaklassen sind etwas anders in 3.x.

Metaklassen sind die geheime Soße, mit der "Klasse" funktioniert. Die Standard-Metaklasse für ein neues Stilobjekt heißt "type".

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

Metaklassen benötigen 3 Args. ' Name ', ' Grundlagen ' und ' Diktat '

Hier beginnt das Geheimnis. Achten Sie darauf, woher name, bases und das dict in dieser Beispielklassendefinition kommen.

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

Definieren wir eine Metaklasse, die zeigt, wie ' Klasse: ' nennt es.

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 nun ein Beispiel, das tatsächlich etwas bedeutet: Die Variablen in der Liste "Attribute" werden automatisch auf die Klasse gesetzt und auf None 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, das Initialised Gewinne durch die Metaklasse init_attributes wird nicht an eine Unterklasse von Initialised .

Hier ist ein noch konkreteres Beispiel, das zeigt, wie Sie die Unterklasse "type" zu einer Metaklasse machen können, die eine Aktion ausführt, wenn die Klasse erstellt wird. Das ist ziemlich knifflig:

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

232voto

kindall Punkte 167554

Andere haben erklärt, wie Metaklassen funktionieren und wie sie in das Python-Typsystem passen. Hier ist ein Beispiel dafür, wozu sie verwendet werden können. In einem Test-Framework, das ich geschrieben habe, wollte ich die Reihenfolge, in der Klassen definiert wurden, festhalten, 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 MyType erhält dann ein Klassenattribut _order die die Reihenfolge festhält, in der die Klassen definiert wurden.

0 Stimmen

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

7 Stimmen

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

0 Stimmen

Richtig, duh. Danke. Mein Code würde das Attribut von MyType bei jeder Instanziierung zurücksetzen und würde das Attribut nie setzen, wenn nie eine Instanz von MyType erstellt wurde. Ups. (Und eine Klasseneigenschaft könnte auch funktionieren, aber im Gegensatz zur Metaklasse bietet sie keinen offensichtlichen Platz zum Speichern des Zählers).

201voto

Antti Rasinen Punkte 9468

Eine Anwendung für Metaklassen ist das automatische Hinzufügen neuer Eigenschaften und Methoden zu einer Instanz.

Zum Beispiel, wenn Sie sich Django-Modelle Ihre Definition ist etwas verwirrend. 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 sind die Person-Objekte jedoch mit allen möglichen nützlichen Methoden gefüllt. Siehe die Quelle für einige erstaunliche Metaklassiker.

13 Stimmen

Werden durch die Verwendung von Metaklassen nicht neue Eigenschaften und Methoden zu einer Klasse und nicht eine Instanz? Soweit ich es verstanden habe, verändert die Metaklasse die Klasse selbst und als Ergebnis können die Instanzen durch die veränderte Klasse anders konstruiert werden. Das könnte für Leute, die versuchen, die Natur einer Metaklasse zu verstehen, ein wenig irreführend sein. Nützliche Methoden auf Instanzen können durch normale Vererbung erreicht werden. Der Verweis auf den Django-Code als Beispiel ist jedoch gut.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X