Frage

Was sind metaclasses und was wir verwenden sie für?

War es hilfreich?

Lösung

Eine Metaklasse ist die Klasse einer Klasse. Eine Klasse definiert, wie eine Instanz der Klasse (das heißt ein Objekt) verhält, während eine Metaklasse definiert, wie eine Klasse verhält. Eine Klasse ist eine Instanz einer Metaklasse.

Während in Python können Sie beliebigen Callables für metaclasses verwenden (wie Jerub zeigt), der bessere Ansatz ist es eine tatsächliche Klasse selbst zu machen. type sind die üblichen Metaklasse in Python. type ist selbst eine Klasse, und es ist seine eigene Art. Sie werden in Python nicht rein so etwas wie type der Lage sein, neu zu erstellen, aber Python betrügt ein wenig. Um Ihre eigene Metaklasse in Python erstellen möchten Sie wirklich nur type Unterklasse.

Eine Metaklasse wird am häufigsten als Klasse-Fabrik eingesetzt. Wenn Sie ein Objekt durch den Aufruf der Klasse erstellen, erstellt Python eine neue Klasse (wenn es die ‚Klasse‘ Anweisung ausführt) durch die Metaklasse Aufruf. In Kombination mit den normalen __init__ und __new__ Methoden, metaclasses daher erlauben Ihnen ‚zusätzliche Dinge‘ zu tun, wenn eine Klasse zu schaffen, wie die neue Klasse mit einiger Registry-Registrierung oder die Klasse mit etwas völlig anderes ersetzen.

Wenn die class Anweisung ausgeführt wird, führt Python zuerst den Körper der class Anweisung als normalen Codeblock. Die sich ergebende Namensraum (a dict) die Attribute der Klasse hält zu sein. Die Metaklasse durch einen Blick auf den Basisklassen der bestimmt werden, Klasse werden (metaclasses vererbt werden), am __metaclass__ Attribute der Klasse sein (falls vorhanden) oder die __metaclass__ globale Variable. Die Metaklasse wird dann mit den Namen, Basen und Attribute der Klasse nannte es zu instanziiert.

Allerdings metaclasses definieren tatsächlich die type eine Klasse, nicht nur eine Fabrik für sie, so dass Sie viel mehr mit ihnen tun. Sie können zum Beispiel definieren normale Methoden auf der Metaklasse. Diese metaclass-Methoden sind wie Class, dass sie ohne eine Instanz von der Klasse aufgerufen werden, aber sie sind auch nicht wie Class in, dass sie nicht auf einer Instanz der Klasse aufgerufen werden. type.__subclasses__() ist ein Beispiel für ein Verfahren auf der type Metaklasse. Sie können auch die normalen ‚Magie‘ Methoden definieren, wie __add__, __iter__ und __getattr__, zu implementieren oder zu ändern, wie die Klasse verhält.

Hier ist ein aggregierter Beispiel der Bits und Stücke:

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__

Andere Tipps

Klassen als Objekte

Vor metaclasses verstehen, müssen Sie Klassen in Python meistern. Und Python hat eine sehr eigenartige Vorstellung davon, was Klassen, von der Smalltalk Sprache entlehnt.

In den meisten Sprachen, Klassen sind nur Teile des Codes, die beschreiben, wie ein Objekt zu erzeugen. Das ist irgendwie wahr in Python zu:

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

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

Aber Klassen sind mehr als das in Python. Die Klassen sind Objekte zu.

Ja, Objekte.

Sobald Sie das Stichwort class verwenden, Python führt es aus und schafft ein Objekt. 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 (Instanzen) zu schaffen, und aus diesem Grunde ist es eine Klasse .

Aber immer noch, es ist ein Ziel, und deshalb:

  • Sie können es auf eine Variable
  • zuweisen
  • Sie können es kopieren
  • Sie Attribute hinzufügen können
  • Sie es als Funktionsparameter übergeben kann

z.

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

Erstellen von Klassen dynamisch

Da Klassen-Objekte sind, können Sie sie on the fly erstellen, wie jedes Objekt.

Als erstes können Sie eine Klasse in einer Funktion class erstellen:

>>> 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 immer noch die ganze Klasse selbst schreiben müssen.

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

Wenn Sie das class Schlüsselwort verwenden, erstellt Python diese Aufgabe automatisch. Aber bei den meisten Dingen in Python, es gibt Ihnen einen Weg, es manuell zu tun.

Denken Sie daran, die Funktion type? Die gute alte Funktion, mit der Sie wissen, was Geben Sie 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 eine ganz andere Fähigkeit hat, kann es auch erstellen von Klassen on the fly. type kann die Beschreibung einer Klasse als Parameter übernehmen, und gibt eine Klasse.

(ich weiß, es ist dumm, dass die gleiche Funktion zwei völlig unterschiedliche Anwendungen entsprechend den Parametern haben, können Sie es passieren. Es ist ein Problem, aufgrund rückwärts Kompatibilität in Python)

type funktioniert so:

type(name of the class,
     tuple of the parent class (for inheritance, can be empty),
     dictionary containing attributes names and values)

z.

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

kann manuell auf diese 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 bemerken, dass wir „MyShinyClass“, wie der Name der Klasse verwenden und als die Variable, die die Klassenreferenz zu halten. Sie können unterschiedlich sein, aber es gibt keinen Grund, die Dinge zu komplizieren.

type nimmt ein Wörterbuch die Attribute der Klasse zu definieren. Also:

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

Kann übersetzt werden:

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

Und wie eine 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 von ihm erben, so:

>>>   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 sollten Sie Methoden, um Ihre Klasse hinzuzufügen. definieren, nur eine Funktion mit der richtigen Signatur und ordnet es als Attribut.

>>> 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 sogar noch mehr Methoden hinzufügen, nachdem Sie dynamisch die Klasse erstellen, ebenso wie Methoden in ein normalerweise 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, wohin wir gehen: in Python, Klassen sind Objekte, und Sie können eine Klasse on the fly, dynamisch erstellen

.

Dies ist, was Python tut, wenn Sie das Schlüsselwort class verwenden, und es tut dies durch eine Metaklasse verwendet wird.

Was ist metaclasses (endlich)

Metaklassen sind das 'Zeug', die Klassen erstellt.

Sie definieren Klassen, um Objekte zu erstellen, nicht wahr?

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

Nun, metaclasses sind, was diese Objekte erstellen. Sie sind die Klassen Klassen, Sie können sie auf diese Weise vorstellen:

MyClass = MetaClass()
my_object = MyClass()

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

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

Es ist, weil die Funktion type in der Tat ist ein Metaklasse. type ist die metaclass Python nutzt alle Klassen hinter den Kulissen zu erstellen.

Nun fragt man sich, warum zum Teufel sie klein geschrieben wird, und nicht Type?

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

Sie sehen, dass durch das __class__ Attribut zu ü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 sie alle haben wurde 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 das __class__ jeden __class__?

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

Also, ein Metaklasse ist nur die Sachen, die Klassenobjekte erstellt.

Sie können nennen es eine ‚Klasse Fabrik‘, wenn Sie es wünschen.

type ist die eingebaute in metaclass Python Anwendungen, aber natürlich können Sie erstellen Ihre eigene Metaklasse.

Das __metaclass__ Attribut

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

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

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

Vorsicht, es ist schwierig.

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

Python für __metaclass__ in der Klassendefinition aussehen wird. Wenn es es findet, es wird es verwenden, um die Objektklasse Foo zu erstellen. Wenn nicht, wird es verwenden type die Klasse zu erstellen.

Lesen Sie

, der mehrmals.

Wenn Sie das tun:

class Foo(Bar):
    pass

Python hat folgende Möglichkeiten:

Gibt es ein __metaclass__ Attribut in Foo?

Wenn ja, in Speichern ein Klasse-Objekt erstellen (sagte ich ein Klassenobjekt, bei mir bleibt hier), mit dem Namen Foo unter Verwendung, was in __metaclass__ ist.

Wenn Python nicht __metaclass__ finden kann, wird es für einen __metaclass__ auf Modulebene schauen und versuchen, das gleiche zu tun (aber nur für Klassen, die etwas nicht erben, im Grunde im alten Stil-Klassen).

Dann, wenn es keine __metaclass__ überhaupt finden kann, wird es die Bar der Verwendung (der erste Elternteil) eigene Metaklasse (Standardeinstellung type sein könnte), um das Klassenobjekt erstellen.

Achten Sie darauf, dass das hier __metaclass__ Attribut wird nicht vererbt werden, die Metaklasse der Eltern (Bar.__class__) wird. Wenn Bar ein __metaclass__ Attribut verwendet, die Bar mit type() erstellt (und nicht type.__new__()), nicht den Unterklassen dieses Verhalten erben.

Nun ist die große Frage ist, was kann man in __metaclass__ setzen?

Die Antwort ist: etwas, das eine Klasse erstellen kann

.

Und was kann eine Klasse erstellen? type, oder alles, was es Unterklassen oder verwendet wird.

Metaklassen in Python 3

Die Syntax der Metaklasse gesetzt wird geändert in Python 3:

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

d. das __metaclass__ Attribut nicht mehr verwendet wird, für ein Keyword-Argument in der Liste der Basisklassen.

Das Verhalten von metaclasses jedoch bleibt weitgehend gleich .

Eine Sache hinzugefügt metaclasses in Python 3 ist, dass Sie auch Attribute als Keyword-Argumente in eine Metaklasse passieren können, etwa so:

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

Lesen Sie den Abschnitt unten, wie Python diese behandelt.

Benutzerdefinierte metaclasses

Der Hauptzweckeine Metaklasse die Klasse automatisch zu ändern, wenn es erstellt.

Sie tun dies in der Regel für APIs, wo Sie wollen Klassen erstellen passende das aktuelle Kontext.

Stellen Sie sich ein dummes Beispiel, wo Sie sich entscheiden, dass alle Klassen in Ihrem Modul sollten in Großbuchstaben haben ihre Attribute geschrieben. Es gibt mehrere Möglichkeiten, dies zu tun, aber eine Möglichkeit ist __metaclass__ auf Modulebene zu setzen.

Auf diese Weise alle Klassen dieses Moduls werden diese Metaklasse erstellt werden, und wir müssen nur die Metaklasse sagen, drehen alle in Großbuchstaben schreibt.

Zum Glück kann __metaclass__ tatsächlich jede aufrufbar sein, es nicht zu sein braucht eine formale Klasse (ich weiß, was mit ‚Klasse‘ in seinem Namen muss nicht sein eine Klasse, go figure ... aber es ist hilfreich).

So werden wir mit einem einfachen Beispiel beginnen, indem Sie 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_attr):
    """
      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_attr = {}
    for name, val in future_class_attr.items():
        if not name.startswith('__'):
            uppercase_attr[name.upper()] = val
        else:
            uppercase_attr[name] = val

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

__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'

print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True

f = Foo()
print(f.BAR)
# Out: 'bip'

Nun wollen wir tun genau das gleiche, aber eine echte Klasse für eine Metaklasse mit:

# 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_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type(future_class_name, future_class_parents, uppercase_attr)

Aber das ist nicht wirklich OOP. Wir nennen type direkt und wir nicht außer Kraft setzen oder rufen Sie die Eltern __new__. Lassen Sie uns es tun:

class UpperAttrMetaclass(type):

    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        # reuse the type.__new__ method
        # this is basic OOP, nothing magic in there
        return type.__new__(upperattr_metaclass, future_class_name,
                            future_class_parents, uppercase_attr)

Sie haben möglicherweise das zusätzliche Argument upperattr_metaclass bemerkt. Es gibt nichts Besonderes daran: __new__ erhält immer die Klasse es in definiert ist, als erster Parameter. Genau wie Sie self für gewöhnliche Methoden haben, die die Instanz als ersten Parameter oder der definierenden Klasse für Klassenmethoden erhalten.

Natürlich sind die Namen, die ich hier verwendet werden, sind lange aus Gründen der Klarheit, aber wie für self, haben alle Argumente Leitbezeichnungen. So eine echte Produktion metaclass würde wie folgt aussehen:

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type.__new__(cls, clsname, bases, uppercase_attr)

Wir machen es noch sauberer durch super verwenden, die Vererbung erleichtern wird (weil ja, können Sie metaclasses haben, von metaclasses vererben, von Typ vererben):

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)

Oh, und in Python 3, wenn Sie tun, um diesen Anruf mit Keyword-Argumente, wie folgt aus:

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

Es übersetzt dies in der Metaklasse, es zu benutzen:

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

Das ist es. Es gibt wirklich nichts mehr über metaclasses.

Der Grund für die Komplexität des Codes metaclasses verwendet, ist nicht da von Metaklassen, dann ist es, weil Sie in der Regel metaclasses verwenden verdrehte Sachen zu tun die sich auf Selbstbeobachtung, Vererbung zu manipulieren, Vars wie __dict__, etc.

Tatsächlich sind metaclasses besonders nützlich, schwarze Magie zu tun, und deshalb komplizierte Sachen. Aber selbst, sie sind einfach:

  • abfangen eine Klasse Schöpfung
  • Ändern Sie die Klasse
  • Rückkehr der modifizierten Klasse

Warum würden Sie metaclasses Klassen statt Funktionen verwenden?

Da __metaclass__ jede aufrufbare annehmen können, warum sollten Sie eine Klasse verwenden da es offensichtlich komplizierter?

Es gibt mehrere Gründe, dies zu tun:

  • Die Absicht ist klar. Wenn Sie UpperAttrMetaclass(type) lesen, wissen Sie, was wird folgen
  • Sie können OOP verwenden. Metaklasse kann von metaclass erbt, übergeordnete Methoden außer Kraft setzen. Metaclasses kann sogar metaclasses verwenden.
  • Subklassen einer Klasse werden Instanzen seiner Metaklasse, wenn Sie eine Metaklasse-Klasse angegeben, aber nicht mit einer Metaklasse-Funktion.
  • Sie können Ihren Code besser strukturieren. Sie verwenden nie metaclasses für etwas wie trivial wie das obige Beispiel. Es ist in der Regel für etwas kompliziert. Mit der Fähigkeit, verschiedene Methoden und gruppieren sie in einer Klasse zu machen, ist sehr nützlich der Code leichter zu lesen.
  • Sie können auf __new__, __init__ und __call__ Haken. Welche ermöglicht Sie verschiedene Dinge zu tun. Auch wenn in der Regel können Sie alles in __new__ tun, Manche Leute sind einfach bequemer mit __init__.
  • Diese werden als metaclasses, verdammt! Es muss etwas bedeuten!

Warum würden Sie metaclEsel?

Nun ist die große Frage. Warum würden Sie etwas obskure Fehler anfällig Funktion verwenden?

Nun, normalerweise Sie dies nicht tun:

  

Metaklassen ist tiefere Magie,   99% der Nutzer sollte nie befürchten.   Wenn Sie sich fragen, ob Sie sie benötigen,   Sie dies nicht tun (die Leute, die tatsächlich   sie müssen wissen mit Sicherheit, dass   sie brauchen sie, und nicht eine brauchen   Erklärung darüber, warum).

Python Guru Tim Peters

Der Hauptanwendungsfall für eine Metaklasse schafft eine API. Ein typisches Beispiel hierfür ist die Django ORM.

Es ermöglicht Ihnen, so etwas zu definieren:

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

Aber wenn Sie dies tun:

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

Es wird kein IntegerField Objekt zurück. Es wird ein int zurückkehren und es auch nehmen kann direkt aus der Datenbank.

Dies ist möglich, weil models.Model definiert __metaclass__ und verwendet es etwas Magie, die die Person Sie gerade definiert mit einfachen Anweisungen schaltet in einen komplexen Haken zu einem Datenbankfeld.

Django macht etwas komplexer Blick einfach durch eine einfache API aussetzt und mit metaclasses, neu zu erstellen Codes aus dieser API den richtigen Job zu tun hinter den Kulissen.

Das letzte Wort

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

Nun, in der Tat, Klassen sind selbst Instanzen. Von Metaklassen.

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

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

Mit Ausnahme type.

type ist eigentlich seine eigene Metaklasse. Das ist nicht etwas, das Sie könnte reproduziert in reinem Python und wird durch Betrug ein wenig bei der Umsetzung getan Ebene.

Zweitens metaclasses sind kompliziert. Sie können sie nicht verwenden möchten, für sehr einfache Klasse Veränderungen. Sie können Klassen ändern, indem zwei verschiedene Techniken:

99% der Zeit, die Sie Klasse Änderung benötigen, sind Sie besser dran, diese mit.

Aber 98% der Zeit, Sie brauchen gar nicht Klasse Änderung.

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

Metaklassen sind die geheime Zutat, die ‚Klasse‘ Arbeit zu machen. Die Standard-Metaklasse für ein neuen Stil Objekt ‚Typ‘ bezeichnet.

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

Metaklassen nehmen 3 args. ' Namen ', ' Basen ' und " dict

Hier ist, wo das Geheimnis beginnt. Suchen Sie nach dem Namen, Basen und die dict kommen aus in diesem Beispiel Klassendefinition.

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

Lassen Sie uns eine Metaklasse definieren, die zeigen, 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 wirklich etwas bedeutet, wird diese automatisch die Variablen in der Liste machen „Attribute“ auf der Klasse gesetzt und auf Keine.

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, dass ‚Initalised‘ Gewinne durch die Metaklasse init_attributes mit nicht auf eine Unterklasse von Initalised geleitet wird.

Hier ist ein noch konkreteres Beispiel, das zeigt, wie Sie eine Unterklasse können ‚Typ‘, um eine Metaklasse zu machen, die eine Aktion ausführt, wenn die Klasse erstellt wird. Das ist ziemlich verzwickt:

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

Eine Verwendung für metaclasses ist das Hinzufügen neuer Eigenschaften und Methoden zu einer Instanz automatisch.

Zum Beispiel, wenn Sie Django Modelle , ihre Definition sieht ein bisschen verwirrend. Es sieht aus, als ob Sie nur Klasseneigenschaften definieren, sind:

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

Doch zur Laufzeit werden die Person Objekte mit allerlei nützlichen Methoden gefüllt. Sehen Sie sich die Quelle für einige erstaunliche metaclassery.

Andere haben erklärt, wie metaclasses Arbeit und wie sie den Python Typ-System passen. Hier ist ein Beispiel dafür, was sie für verwendet werden. In einem Test-Framework Ich schrieb, wollte ich den Überblick über die Ordnung zu halten, in denen Klassen wurden definiert, so dass ich sie später in dieser Reihenfolge instanziiert könnte. 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 ist bekommt dann eine Klasse Attribut _order, die die Reihenfolge, in der Aufzeichnungen wurden die Klassen definiert.

Ich denke, die ONLamp Einführung Programmierung Metaklasse ist gut geschrieben und gibt eine wirklich gute Einführung in das Thema trotz bereits mehr Jahre alt zu sein.

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

Kurz gesagt: Eine Klasse eine Vorlage für die Erstellung einer Instanz ist, ist ein Metaklasse ein Entwurf für die Schaffung einer Klasse. Es kann leicht, dass Klassen in Python zu sehen müssen auch erstklassig seine Objekte dieses Verhalten zu ermöglichen.

Ich habe noch nie eine selbst geschrieben, aber ich denke, eine der schönsten Verwendungen von Metaklassen können im Rahmen Django zu sehen . Die Modellklassen verwenden, um einen metaclass Ansatz einen deklarativen Stil des Schreibens neue Modelle oder Formularklassen zu ermöglichen. Während die Metaklasse der Klasse schafft, erhalten alle Mitglieder die Möglichkeit, die Klasse selbst anpassen.

Das, was übrig bleibt, ist zu sagen:. Wenn Sie nicht wissen, was metaclasses sind, ist die Wahrscheinlichkeit, dass Sie auf werden sie nicht brauchen, 99%

  

Was sind metaclasses? Was tun Sie sie für?

TLDR: Ein metaclass instanziiert und definiert das Verhalten für eine Klasse wie eine Klasse instanziiert und definiert das Verhalten für eine Instanz.

Pseudocode:

>>> Class(...)
instance

Die oben sollte Ihnen bekannt vorkommen. Nun, wo kommt Class her? Es ist eine Instanz einer Metaklasse (auch Pseudo-Code):

>>> Metaclass(...)
Class

Im wirklichen Code können wir die Standard-Metaklasse passieren, type, alles, was wir brauchen, um eine Klasse zu instanziieren und wir bekommen eine Klasse:

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

Um es anders

  • Eine Klasse ist eine Instanz als Metaklasse zu einer Klasse ist.

    Wenn wir ein Objekt instanziiert, erhalten wir eine Instanz:

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

    Auch wenn wir eine Klasse explizit mit dem Standard-Metaklasse, type definieren wir instanziiert es:

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

    >>> isinstance(object, type)
    True
    
  • einen dritten Weg Stoßen, eine Metaklasse ist eine Klasse, die Klasse.

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

Wenn Sie eine Klassendefinition schreiben und Python führt es aus, eine Metaklasse verwendet das Klassenobjekt zu instanziiert (das wird wiederum verwendet werden, Instanzen dieser Klasse zu instanziiert).

So wie wir Klassendefinitionen verwenden können, um zu ändern, wie benutzerdefinierte Objektinstanzen verhalten, können wir eine Metaklasse Klassendefinition verwenden, um die Art und Weise ein Klassenobjekt verhält sich zu ändern.

Was können sie verwendet werden? Von der docs :

  

Das Potential nutzt für metaclasses grenzenlos sind. Einige Ideen, die untersucht wurden, umfassen die Protokollierung, Schnittstellenkontrolle, automatische Übertragung, automatisches Eigentum Erstellung, Proxies, Frameworks und automatische Ressourcen Verriegelungs- / Synchronisation.

Dennoch ist es in der Regel für die Benutzer aufgefordert, mit metaclasses, wenn es unbedingt notwendig, zu vermeiden.

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

Wenn Sie eine Klassendefinition schreiben, zum Beispiel so,

class Foo(object): 
    'demo'

Sie instanziiert ein Klassenobjekt.

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

Es ist die gleiche wie funktionell type mit den entsprechenden Argumenten aufrufen und das Ergebnis in eine Variable dieses Namens zuweisen:

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

Beachten Sie, automatisch einige Dinge, an die __dict__ hinzukommen, das heißt der Namespace:

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

Die metaclass des Objekts wir geschaffen, in beiden Fällen ist type.

(Seite A-Note auf den Inhalt der Klasse __dict__: __module__ ist da, weil Klassen müssen wissen, wo sie definiert sind, und __dict__ und __weakref__ sind da, weil wir __slots__ nicht definieren - wenn wir ein href <= "https : //stackoverflow.com/q/472000/541136" > definieren __slots__ wir ein wenig Platz in den Fällen sparen werden, wie wir __dict__ und __weakref__ durch Ausschließen sie nicht zulassen kann. Zum Beispiel:

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

... aber ich schweife ab.)

Wir erweitern type wie jede andere Klassendefinition:

Hier ist der Standard __repr__ die Klassen:

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

Eines der wertvollsten Dinge, die wir standardmäßig tun können, ein Python-Objekts in schriftlicher Form ist es mit einem guten __repr__ zu bieten. Wenn wir help(repr) nennen lernen wir, dass es ein guter Test für einen __repr__ ist, die auch einen Test für die Gleichstellung erfordert - obj == eval(repr(obj)). Die folgende einfache Implementierung von __repr__ und __eq__ für Klasseninstanzen unserer Typklasse bietet uns eine Demonstration, die auf 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__)

Jetzt Wenn wir also ein Objekt mit einem diesem Metaklasse, die __repr__ auf der Kommandozeile hallte einen viel weniger hässlichen Anblick als der Standard:

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

Mit einem schönen __repr__ für die Klasseninstanz definiert, haben wir eine stärkere Fähigkeit, unseren Code zu debuggen. Jedoch viel weiter mit eval(repr(Class)) Überprüfung ist unwahrscheinlich (als Funktionen eher unmöglich wären, von ihrem Standard __repr__ des eval).

Eine erwartete Nutzung: __prepare__ ein Namespace

Wenn Sie zum Beispiel wollen wir in wissen, was zu bestellen sind in einer Klasse Methoden erstellt, könnten wir eine geordnete dict als Namensraum der Klasse bieten. Wir tun dies mit __prepare__ die den Namespace dict für die Rückkehr Klasse, wenn es in Python implementiert 3 :

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 Nutzung:

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

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

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

Hinweis wurde dieses Beispiel aus der Dokumentation angepasst - die neue enum in der Standardbibliothek dies der Fall ist.

Also, was wir taten, war eine Metaklasse instanziiert durch Erstellen einer Klasse. Wir können behandeln auch die Metaklasse wie wir jede andere Klasse. Es hat eine Methode Auflösung Reihenfolge:

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

Und es hat in etwa den richtigen repr (die wir nicht mehr eval, wenn wir einen Weg, um unsere Funktionen darzustellen finden können.):

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

Python 3-Update

Es gibt (an dieser Stelle) zwei wichtige Methoden in einer Metaklasse:

  • __prepare__ und
  • __new__

__prepare__ können Sie eine benutzerdefinierte Zuordnung (wie ein OrderedDict) liefern als Namensraum verwendet werden, während die Klasse erstellt wird. Sie müssen eine Instanz zurückgeben, was auch immer Namespace Sie wählen. Wenn Sie nicht über eine normale __prepare__ implementieren dict verwendet wird.

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

Eine nackte Knochen, do-nichts-extra metaclass 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 einige einfache Validierung Code auf Ihrer Attribute laufen - wie es immer ein int oder ein str sein muss. Ohne eine Metaklasse, würde Ihre Klasse in etwa so aussehen:

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

Wie Sie sehen können, müssen Sie den Namen des Attributs zweimal wiederholen. Dies macht Fehler möglich zusammen mit irritierenden Fehler.

Ein einfaches metaclass kann dieses Problem lösen:

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

Dies ist, was die Metaklasse wie (nicht mit __prepare__, da es nicht nötig ist) aussehen:

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'

erzeugt:

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, um es auch mit einer Klasse Dekorateur erreicht worden sein könnte, aber vermutlich eine tatsächliche metaclass würde viel mehr tun

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

Rolle eines metaclass' __call__() Methode beim Erstellen einer Klasseninstanz

Wenn Sie Python-Programmierung für mehr als ein paar Monate getan haben, werden Sie schließlich auf Code stolpern, die wie folgt 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 für die 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 aufrufbare verwendet wird. Aber wie wir aus früheren Antworten gesehen haben eine Klasse selbst ist eine Instanz einer Metaklasse, so dass, wenn wir die Klasse als aufrufbare verwenden (das heißt, wenn wir eine Instanz davon) wir fordern tatsächlich seine Metaklasse __call__() Methode. An diesem Punkt die meisten Python-Programmierer sind ein bisschen verwirrt, weil sie, dass bereits gesagt habe, wenn eine Instanz wie diese instance = SomeClass() Erstellung sind Sie seine __init__() Methode aufrufen. Einige, die ein wenig tiefer gegraben haben, wissen, dass vor __init__() es __new__(). Nun, heute eine andere Schicht der Wahrheit wird enthüllt, bevor __new__() dort die __call__() Metaklasse ist.

Lassen Sie uns die Methode Call-Kette studieren aus speziell der Perspektive eine Instanz einer Klasse zu schaffen.

Dies ist ein Metaklasse, die genau in dem Moment anmeldet, bevor eine Instanz erstellt wird, und dem Moment, es zu ist es zurückzukehren.

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 die 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 nun eine Instanz von Class_1 erstellen

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 Code oben eigentlich alles tun, um nicht mehr als die Aufgaben anzumelden. Jede Methode delegiert die eigentliche Arbeit an ihre Mutter Implementierung, und hält so das Standardverhalten. Da type Meta_1 die Elternklasse (type ist der übergeordnete Standard metaclass) ist und die Reihenfolge Folge des Ausgangs oben bedenkt, haben wir jetzt eine Ahnung, was die Pseudo-Implementierung von type.__call__() wäre:

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 Metaklasse ist derjenige, der zuerst genannt wird. Es dann Delegierten Schaffung der Instanz an die __new__() Methode der Klasse und der Initialisierung der __init__() der Instanz. Es ist auch derjenige, der letztlich die Instanz zurückgibt.

Aus dem Vorstehenden ergibt sich, dass die __call__() metaclass auch die Möglichkeit gegeben wird, um zu entscheiden, ob ein Anruf Class_1.__new__() oder Class_1.__init__() schließlich gemacht werden. Im Laufe seiner Ausführung könnte es tatsächlich ein Objekt zurückgeben, die nicht durch eine dieser Methoden berührt hat. Nehmen Sie zum Beispiel diesen Ansatz zum Singletonmuster:

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__()."

Lassen Sie uns beobachten, was passiert, wenn sie wiederholt ein Objekt vom Typ Class_2 zu erstellen versuchen,

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

Eine Metaklasse ist eine Klasse, die erzählt, wie (etwas) andere Klasse geschaffen werden.

Dies ist ein Fall, in dem ich metaclass als eine Lösung für mein Problem sah: Ich hatte ein wirklich kompliziertes Problem, das wahrscheinlich anders hätte gelöst werden konnte, aber ich entschied, es zu lösen eine Metaklasse verwenden. Aufgrund der Komplexität, ist es eines der wenigen Module, die ich geschrieben habe, in dem die Kommentare in dem Modul die Menge an Code übertreffen, die geschrieben wurde. Hier ist sie ...

#!/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()

type ist eigentlich ein metaclass - eine Klasse, die einen anderen Klassen schafft. Die meisten metaclass sind die Unterklassen von type. Die metaclass erhält die new Klasse als erstes Argument und mit Details Zugang zum Klassenobjekt zur Verfügung stellen, wie unten erwähnt:

>>> 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 nicht jederzeit instanziiert wurde; der einfache Akt der Klasse ausgelöst Ausführung des metaclass zu schaffen.

Die tl; dr Version

Die type(obj) Funktion bekommt man die Art eines Objekts.

Die type() einer Klasse ist die metaclass .

Um eine Metaklasse zu verwenden:

class Foo(object):
    __metaclass__ = MyMetaClass

Python-Klassen sind selbst Objekte - wie in Beispiel - ihre Meta-Klasse.

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

class foo:
    ...

Meta-Klasse verwendet werden, eine Regel auf eine ganze Reihe von Klassen anzuwenden. Zum Beispiel: Angenommen, Sie ein ORM Gebäude sind auf eine Datenbank zuzugreifen, und Sie möchten Datensätze aus jeder Tabelle einer Klasse dieser Tabelle zugeordnet werden (basierend auf den Feldern, Geschäftsregeln, etc ..,), eine mögliche Verwendung von metaclass ist zum Beispiel Verbindungspool-Logik, die aus allen Tabellen Anteil von allen Klassen der Aufzeichnung ist. Eine weitere Anwendung ist die Logik, um Fremdschlüssel zu unterstützen, die mehrere Klassen von Datensätzen beinhaltet.

Wenn Sie definieren metaclass, Sie Unterklasse-Typ und kann die folgenden magischen Methoden overrided 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

Wie auch immer, sind diese beiden die am häufigsten verwendeten Haken. metaclassing ist mächtig, und oben ist bei weitem nicht und erschöpfende Liste von Anwendungen für metaclassing.

Der Typ () Funktion kann den Typ eines Objekts zurückgeben oder einen neuen Typ erstellen,

zum Beispiel, können wir eine Hallo-Klasse mit der Typ () Funktion erstellen und müssen nicht auf diese Weise verwenden, um mit Klasse Hallo (Objekt):

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

Neben der Verwendung von Typ () dynamisch Klassen zu erstellen, können Sie Erstellung Verhalten der Klasse steuern und metaclass verwenden.

Nach dem Python-Objektmodell ist die Klasse das Objekt, so dass die Klasse eine Instanz einer anderen bestimmten Klasse sein muss. Standardmäßig ist eine Python-Klasse Instanz der Typklasse. Das heißt, Typ metaclass der meisten der integrierten Klassen und metaclass von benutzerdefinierten Klassen ist.

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 Keyword-Argumente in metaclass übergeben, es gibt den Python-Interpreter die Custom durch ListMetaclass zu erstellen. neue () an diesem Punkt können wir die Klassendefinition ändern, zum Beispiel, und eine neue Methode hinzufügen und dann die überarbeiteten Definition zurück.

Zusätzlich zu den veröffentlichten Antworten kann ich sagen, dass ein metaclass das Verhalten für eine Klasse definiert. So können Sie ausdrücklich Ihre metaclass gesetzt. Jedes Mal, wenn Python ein Schlüsselwort class bekommt dann geht es für die metaclass suchen. Wenn es nicht gefunden wird - wird der Standard-Metaklasse Typ verwendet die Klasse des Objekt zu erstellen. Mit Hilfe des __metaclass__ Attributs können Sie metaclass Ihre Klasse festgelegt:

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

print(MyClass.__metaclass__)

Es wird die Ausgabe wie folgt erzeugen:

class 'type'

Und natürlich können Sie Ihre eigene metaclass erstellen, das Verhalten einer Klasse zu definieren, die Ihre Klasse erstellt werden.

zu tun, dass Ihre Standard-metaclass Typklasse vererbt werden muss, da dies das Haupt metaclass ist:

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

Der Ausgang wird sein:

class '__main__.MyMetaClass'
class 'type'
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top