Frage Was sind Metaklassen in Python?


Was sind Metaklassen und wofür verwenden wir sie?


4572
2017-09-19 06:10


Ursprung


Antworten:


Eine Metaklasse ist die Klasse einer Klasse. Wenn eine Klasse definiert, wie sich eine Instanz der Klasse verhält, definiert eine Metaklasse, wie sich eine Klasse verhält. Eine Klasse ist eine Instanz einer Metaklasse.

metaclass diagram

Während in Python können Sie beliebige Callables für Metaklassen (wie Jerub zeigt, ist der nützlichste Ansatz tatsächlich, es zu einer eigentlichen Klasse zu machen. type ist die übliche Metaklasse in Python. Falls Sie sich fragen, ja, type ist selbst eine Klasse, und es ist ein eigener Typ. Sie werden nicht in der Lage sein, etwas wie neu zu erstellen type rein in Python, aber Python schummelt ein wenig. Um Ihre eigene Metaklasse in Python zu erstellen, möchten Sie nur eine Unterklasse erstellen type.

Eine Metaklasse wird am häufigsten als Klassenfabrik verwendet. Wenn Sie eine Instanz der Klasse erstellen, indem Sie die Klasse aufrufen, erstellt Python eine neue Klasse (wenn sie die Anweisung 'class' ausführt), indem sie die Metaklasse aufruft. Kombiniert mit dem Normalen __init__ und __new__ Methoden, Metaklassen ermöglichen es Ihnen daher, zusätzliche Dinge zu tun, wenn Sie eine Klasse erstellen, wie das Registrieren der neuen Klasse in einer Registry, oder sogar das Ersetzen der Klasse durch etwas ganz anderes.

Wenn das class Anweisung ausgeführt wird, führt Python zuerst den Rumpf der class Anweisung als normaler Codeblock. Der resultierende Namespace (ein Dict) enthält die Attribute der zukünftigen Klasse. Die Metaklasse wird durch Betrachten der Basisklassen der zu erwerbenden Klasse (Metaklassen werden vererbt) bei der __metaclass__ Attribut der Klasse (falls vorhanden) oder der __metaclass__ Globale Variable. Die Metaklasse wird dann mit dem Namen, den Basen und Attributen der Klasse aufgerufen, um sie zu instanziieren.

Metaklassen definieren jedoch die Art einer Klasse, nicht nur einer Fabrik dafür, damit du viel mehr mit ihnen machen kannst. Sie können zum Beispiel normale Methoden für die Metaklasse definieren. Diese Metaklassen-Methoden sind wie Klassenmethoden, da sie für die Klasse ohne eine Instanz aufgerufen werden können, aber sie sind auch nicht wie Klassenmethoden, da sie für eine Instanz der Klasse nicht aufgerufen werden können. type.__subclasses__() ist ein Beispiel für eine Methode auf der type Metaklasse. Sie können auch die normalen "magischen" Methoden definieren, wie __add__, __iter__ und __getattr__, um zu implementieren oder zu ändern, wie sich die Klasse verhält.

Hier ist ein aggregiertes Beispiel für die 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__

2079
2017-09-19 07:01



Klassen als Objekte

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

In den meisten Sprachen sind Klassen nur Codeabschnitte, die beschreiben, wie ein Objekt erzeugt 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, Python führt 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) ist selbst in der Lage Objekte (die Instanzen) zu erzeugen, und deshalb ist es eine Klasse.

Aber trotzdem ist es ein Objekt und daher:

  • Sie können es einer Variablen zuweisen
  • Sie können es kopieren
  • Sie können 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 andere Objekt erstellen.

Zunächst können Sie mit einer Funktion in einer Funktion eine Klasse erstellen 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 du die ganze Klasse noch selbst schreiben musst.

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

Wenn du das benutzt class Schlüsselwort, Python erstellt dieses Objekt automatisch. Aber mit den meisten Dingen in Python, gibt es Ihnen eine Möglichkeit, es manuell zu tun.

Erinnere dich an die Funktion type? Die gute alte Funktion, die Sie wissen lässt, was Geben Sie ein Objekt ein:

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

Gut, type hat eine völlig andere Fähigkeit, es kann auch Klassen im laufenden Betrieb erstellen. type kann die Beschreibung einer Klasse als Parameter nehmen, und eine Klasse zurückgeben.

(Ich weiß, es ist albern, dass die gleiche Funktion zwei völlig unterschiedliche Anwendungen haben kann, je nachdem, welche Parameter Sie übergeben. Es ist ein Problem aufgrund von 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.B.:

>>> 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 feststellen, dass wir "MyShinyClass" als Name der Klasse verwenden und als die Variable, die die Klassenreferenz enthält. Sie können anders sein, aber es gibt keinen Grund, Dinge zu komplizieren.

type akzeptiert 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 es 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 sogar noch weitere Methoden hinzufügen, nachdem Sie die Klasse dynamisch erstellt haben, genau wie beim Hinzufügen von Methoden zu einem normalerweise erstellten Klassenobjekt.

>>> 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 dynamisch eine Klasse dynamisch erstellen.

Dies ist, was Python tut, wenn Sie das Schlüsselwort verwenden classund zwar mithilfe einer Metaklasse.

Was sind Metaklassen (endlich)

Metaklassen sind das Zeug, das Klassen schafft.

Sie definieren Klassen, um Objekte zu erstellen, oder?

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

Nun, Metaklassen schaffen diese Objekte. Sie sind die Klassen der Klassen, Du kannst sie dir so vorstellen:

MyClass = MetaClass()
my_object = MyClass()

Das hast du gesehen type können Sie so etwas tun:

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

Es liegt an der Funktion type ist in der Tat eine Metaklasse. type ist der Metaklasse Python verwendet, um alle Klassen hinter den Kulissen zu erstellen.

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

Nun, ich denke, es ist eine Frage der Übereinstimmung mit str, die Klasse, die erstellt Zeichenfolgenobjekte und intdie Klasse, die Integer-Objekte erstellt. type ist nur die Klasse, die Klassenobjekte erstellt.

Sie sehen das, indem Sie die __class__ Attribut.

Alles, und ich meine alles, ist ein Objekt in Python. Das beinhaltet Ints, Strings, Funktionen und Klassen. Sie alle sind Objekte. Und alle haben sie 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__ von irgendwelchen __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 der Stoff, der Klassenobjekte erzeugt.

Sie können es eine "Klassenfabrik" nennen, wenn Sie es wünschen.

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

Das __metaclass__ Attribut

Sie können ein hinzufügen __metaclass__ Attribut beim Schreiben einer Klasse:

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

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

Vorsicht, es ist schwierig.

Du schreibst class Foo(object) zuerst, aber das Klassenobjekt Foo ist nicht erstellt in Erinnerung noch.

Python wird danach suchen __metaclass__ in der Klassendefinition. Wenn es es findet, Es wird verwendet, um die Objektklasse zu erstellen Foo. Wenn nicht, wird es verwendet type um die Klasse zu erstellen.

Lesen Sie das mehrmals.

Wenn Sie das tun:

class Foo(Bar):
    pass

Python macht folgendes:

Gibt es ein __metaclass__ Attribut in Foo?

Wenn ja, erstelle im Speicher ein Klassenobjekt (ich sagte ein Klassenobjekt, bleib hier bei mir), mit dem Namen Foo indem wir verwenden, was drin ist __metaclass__.

Wenn Python nicht finden kann __metaclass__Es wird nach einem suchen __metaclass__ auf der MODULE-Ebene und versuchen, das gleiche zu tun (aber nur für Klassen, die nichts erben, im Grunde alte Klassen).

Dann, wenn es keine finden kann __metaclass__ überhaupt wird es das verwenden Bar's (das erste Elternteil) eigene Metaklasse (die möglicherweise die Standardeinstellung ist) type) um das Klassenobjekt zu erstellen.

Sei vorsichtig, dass die __metaclass__ Attribut wird nicht vererbt, die Metaklasse des Elternteils (Bar.__class__) wird sein. Ob Bar verwendet a __metaclass__ Attribut, das erstellt wurde Bar mit type() (und nicht type.__new__()), erben die Unterklassen dieses Verhalten nicht.

Jetzt ist die große Frage, was können Sie hineinlegen? __metaclass__ ?

Die Antwort ist: Etwas, das eine Klasse erstellen kann.

Und was kann eine Klasse schaffen? typeoder irgendetwas, das es unterklassifiziert oder benutzt.

Benutzerdefinierte Metaklassen

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

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

Stellen Sie sich ein dummes Beispiel vor, bei dem Sie alle Klassen in Ihrem Modul entscheiden sollten ihre Attribute in Großbuchstaben geschrieben haben. Es gibt mehrere Möglichkeiten Tun Sie dies, aber ein Weg ist es, zu setzen __metaclass__ auf Modulebene.

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

Glücklicherweise, __metaclass__ kann tatsächlich aufrufbar sein, es muss kein sein formale Klasse (ich weiß, etwas mit "Klasse" in seinem Namen muss nicht sein eine Klasse, geh Figur ... aber es ist hilfreich).

Wir beginnen 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_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, machen wir genau dasselbe, aber verwenden Sie eine echte Klasse für eine Metaklasse:

# 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 überschreiben nicht oder rufe den Elternteil an __new__. Machen wir das:

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 vielleicht das zusätzliche Argument bemerkt upperattr_metaclass. Es gibt nichts besonderes daran: __new__ erhält immer die Klasse, in der es definiert ist, als ersten Parameter. Genau wie du self für gewöhnliche Methoden, die die Instanz als ersten Parameter oder die definierende Klasse für Klassenmethoden erhalten.

Natürlich sind die Namen, die ich hier verwendet, der Klarheit wegen aber lang zum selfAlle Argumente haben konventionelle Namen. Also eine echte Produktion Metaklasse würde so 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 können es noch sauberer machen super, wodurch die Vererbung erleichtert wird (weil ja Sie Metaklassen haben können, die von Metaklassen erben und vom Typ erben):

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)

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

Der Grund für die Komplexität des Codes, der Metaklassen verwendet, liegt nicht darin von Metaklassen, weil Sie normalerweise Metaklassen verwenden, um verdrehte Sachen zu machen sich auf Introspektion verlassen, Vererbung manipulieren, vars wie __dict__, etc.

In der Tat sind Metaklassen besonders nützlich, um schwarze Magie zu machen komplizierte Sachen. Aber für sich sind sie einfach:

  • Abfangen einer Klassenerstellung
  • Modifizieren Sie die Klasse
  • Gib die modifizierte Klasse zurück

Warum würden Sie Metaklassen-Klassen anstelle von Funktionen verwenden?

Schon seit __metaclass__ kann jede aufrufbar akzeptieren, warum würden Sie eine Klasse verwenden da ist es offensichtlich komplizierter?

Dafür gibt es mehrere Gründe:

  • Die Absicht ist klar. Wenn du liest UpperAttrMetaclass(type), Wissen Sie was wird folgen
  • Sie können OOP verwenden. Metaklasse kann von Metaklasse erben, überschreiben übergeordnete Methoden. Metaklassen können sogar Metaklassen verwenden.
  • Unterklassen einer Klasse werden Instanzen ihrer Metaklasse sein, wenn Sie eine Metaklasse-Klasse, aber nicht eine Metaklassen-Funktion angegeben haben.
  • Sie können Ihren Code besser strukturieren. Sie verwenden nie Metaklassen für etwas als trivial wie das obige Beispiel. Es ist normalerweise etwas Kompliziertes. Das haben Die Fähigkeit, 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 anhängen __new__, __init__ und __call__. Was wird erlauben Du kannst verschiedene Sachen machen. Selbst wenn Sie normalerweise alles tun können __new__, Manche Leute sind einfach komfortabler __init__.
  • Diese heißen Metaklassen, verdammt! Es muss etwas bedeuten!

Warum würden Sie Metaklassen verwenden?

Jetzt die große Frage. Warum würden Sie ein obskures fehleranfälliges Feature verwenden?

Naja, normalerweise tust du nicht:

Metaklassen sind tiefer Magie das   99% der Nutzer sollten sich nie Sorgen machen.   Wenn Sie sich fragen, ob Sie sie brauchen,   Sie nicht (die Leute, die tatsächlich   müssen sie mit Sicherheit wissen, dass   Sie brauchen sie und brauchen keine   Erklärung über 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.

Es erlaubt Ihnen, etwas wie folgt zu definieren:

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

Aber wenn du das tust:

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

Es wird nicht zurückgegeben IntegerField Objekt. Es wird ein zurückgeben intund kann es sogar direkt aus der Datenbank übernehmen.

Dies ist möglich, weil models.Model definiert __metaclass__ und es verwendet etwas Magie, die das drehen wird Person Sie haben gerade mit einfachen Aussagen definiert in einen komplexen Hook zu einem Datenbankfeld.

Django macht etwas Komplexes einfach, indem es eine einfache API bereitstellt und mithilfe von Metaklassen Code aus dieser API neu erstellen, um die eigentliche Aufgabe zu erledigen hinter den Kulissen.

Das letzte Wort

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

Tatsächlich sind Klassen selbst Instanzen. Von Metaklassen.

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

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

Ausser für type.

type ist eigentlich eine eigene Metaklasse. Das ist nichts, was du konntest reproduzieren in reinem Python, und wird getan, indem ein wenig bei der Implementierung betrogen wird Niveau.

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

In 99% der Fälle, in denen Sie eine Klassenänderung benötigen, sollten Sie diese besser nutzen.

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


5755
2017-09-19 06:26



Hinweis: Diese Antwort bezieht sich auf Python 2.x, wie es 2008 geschrieben wurde. Die Metaklassen unterscheiden sich geringfügig in 3.x, siehe Kommentare.

Metaklassen sind die geheime Soße, die "Klasse" Arbeit machen. Die Standardmetaklasse für ein neues Stilobjekt wird "Typ" genannt.

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

Metaklassen nehmen 3 Args. "Name","Basen' und 'dict"

Hier beginnt das Geheimnis. Suchen Sie in dieser beispielhaften Klassendefinition nach dem Namen, den Basen und dem Diktat.

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

Lassen Sie uns eine Metaklasse definieren, die zeigt, wie 'Klasse:ruft es an.

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, werden die Variablen in der Liste "attributes" 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 'Initalised' durch die Metaklasse erhält init_attributes wird nicht an eine Unterklasse von Initalise übergeben.

Hier ist ein noch konkreteres Beispiel, das zeigt, wie Sie "type" von der Unterklasse ableiten können, um eine Metaklasse zu erstellen, die eine Aktion ausführt, wenn die Klasse erstellt wird. 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

311
2017-09-19 06:45



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

Zum Beispiel, wenn Sie schauen Django-Modelle, ihre Definition sieht ein bisschen verwirrend aus. Es sieht so aus, als ob 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 Methoden gefüllt. Siehe die Quelle für eine erstaunliche Metaklasse.


124
2018-06-21 16:30



Andere haben erklärt, wie Metaklassen funktionieren und wie sie in das Python-System passen. Hier ist ein Beispiel dafür, wofür sie verwendet werden können. In einem Test-Framework, das ich geschrieben habe, wollte ich die Reihenfolge, in der Klassen definiert wurden, im Auge behalten, damit ich sie später in dieser Reihenfolge instantiieren konnte. Am einfachsten fand ich das mit einer Metaklasse.

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 Das zeichnet die Reihenfolge auf, in der die Klassen definiert wurden.


117
2017-09-19 06:32



Ich denke, die ONLamp-Einführung in die Metaklassen-Programmierung ist gut geschrieben und gibt trotz einiger Jahre eine wirklich gute Einführung in das Thema.

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html

Kurz gesagt: Eine Klasse ist ein Entwurf für die Erstellung einer Instanz, eine Metaklasse ist ein Entwurf 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 nie eines geschrieben, aber ich denke, eine der schönsten Anwendungen von Metaklassen ist in der Django Rahmen. 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 ist, ist: Wenn Sie nicht wissen, was Metaklassen sind, ist die Wahrscheinlichkeit, dass Sie werde sie nicht brauchen ist 99%.


86
2017-08-10 23:28



Was sind Metaklassen? Wofür benutzt du sie?

TLDR: Eine Metaklasse instanziiert und definiert das Verhalten für eine Klasse genau wie eine Klasse das Verhalten für eine Instanz instanziiert und definiert.

Pseudocode:

>>> Class(...)
instance

Das obige sollte Ihnen bekannt vorkommen. Nun, wo ist das? Class komme aus? Es ist eine Instanz einer Metaklasse (auch Pseudocode):

>>> Metaclass(...)
Class

In echtem Code können wir die Standardmetaklasse übergeben, 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'>

Anders ausgedrückt

  • Eine Klasse ist für eine Instanz wie eine Metaklasse eine 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 Standardmetaklasse definieren, typeWir instanziieren es:

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

    >>> isinstance(object, type)
    True
    
  • Setzen Sie einen dritten Weg, eine Metaklasse ist eine Klasse.

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

Wenn Sie eine Klassendefinition schreiben und Python sie ausführt, verwendet sie eine Metaklasse, um das Klassenobjekt zu instanziieren (das wiederum verwendet wird, um Instanzen dieser Klasse zu instanziieren).

Genauso wie wir Klassendefinitionen verwenden können, um das Verhalten von benutzerdefinierten Objektinstanzen zu ändern, können wir eine Metaklassenklassendefinition verwenden, um die Verhaltensweise eines Klassenobjekts zu ändern.

Wofür können sie verwendet werden? Von dem Dokumente:

Die potenziellen Verwendungsmöglichkeiten für Metaklassen sind unbegrenzt. Einige Ideen, die untersucht wurden, umfassen Protokollierung, Schnittstellenprüfung, automatische Delegierung, automatische Eigenschaftenerstellung, Proxies, Frameworks und automatische Ressourcensperrung / -synchronisierung.

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

Sie verwenden eine Metaklasse jedes Mal, 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)

Es ist das gleiche wie funktionales Anrufen type mit den entsprechenden Argumenten und Zuweisung des Ergebnisses zu einer Variablen dieses Namens:

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

Beachten Sie, dass einige Dinge automatisch hinzugefügt werden __dict__, d. h. der Namespace:

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

Das Metaklasse des Objekts, das wir in beiden Fällen geschaffen haben type.

(Eine Randnotiz zum Inhalt der Klasse __dict__: __module__ ist da, weil Klassen wissen müssen, wo sie definiert sind, und __dict__ und __weakref__ sind da, weil wir nicht definieren __slots__ - wenn wir definieren __slots__ Wir sparen etwas Platz in den Instanzen, da wir nicht zulassen können __dict__ und __weakref__ indem man sie ausschließt. Beispielsweise:

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

... Aber ich schweife ab.)

Wir können verlängern type genau wie jede andere Klassendefinition:

Hier ist die Standardeinstellung __repr__ von Klassen:

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

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

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, wird die __repr__ echoed auf der Kommandozeile bietet eine viel weniger hässliche Sicht 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 netten __repr__ Für die Klasseninstanz definiert, können wir unseren Code besser debuggen. Allerdings viel weiter prüfen mit eval(repr(Class)) ist unwahrscheinlich (Funktionen wären aus dem Standard nicht zu erkennen) __repr__(s).

Eine erwartete Verwendung: __prepare__ ein Namespace

Wenn wir beispielsweise wissen möchten, in welcher Reihenfolge die Methoden einer Klasse erstellt werden, könnten wir ein geordnetes dict als Namespace der Klasse bereitstellen. Wir würden das mit machen __prepare__ welche Gibt das Namespace-Dict für die Klasse zurück, 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 einen Datensatz 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 - das neue Aufzählung in der Standardbibliothek macht dies.

Also haben wir 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 beurteilen können, wenn wir nicht einen Weg finden, unsere Funktionen zu repräsentieren.):

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

74
2018-03-01 19:48



Python 3 Aktualisierung

Es gibt (an dieser Stelle) zwei Schlüsselmethoden in einer Metaklasse:

  • __prepare__, und
  • __new__

__prepare__ können Sie ein benutzerdefiniertes Mapping bereitstellen (z. B. ein OrderedDict) als Namespace während der Erstellung der Klasse verwendet werden. Sie müssen eine Instanz des von Ihnen ausgewählten Namespace zurückgeben. Wenn Sie nicht implementieren __prepare__eine normale dict wird eingesetzt.

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

Eine blanke, do-nothing-extra Metaklasse würde gerne:

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 einen einfachen Validierungscode, der auf Ihren Attributen ausgeführt werden soll - so wie es immer sein muss int oder ein str. Ohne eine Metaklasse würde deine Klasse ungefähr 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 Tippfehler zusammen mit irritierenden Bugs möglich.

Eine einfache Metaklasse kann dieses Problem beheben:

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

So würde die Metaklasse aussehen (nicht verwenden __prepare__ da es 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 Beispiellauf 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 könnte auch mit einem Klassen-Dekorator durchgeführt worden sein, aber vermutlich würde eine tatsächliche Metaklasse viel mehr tun.

Die Klasse 'ValidateType' 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

55
2017-10-13 09:21



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

Dies ist ein Fall, in dem ich Metaklasse als eine Lösung für mein Problem sah: Ich hatte ein wirklich kompliziertes Problem, das hätte wahrscheinlich anders gelöst werden können, aber ich entschied mich dafür, es mit einer Metaklasse zu lösen. Wegen der Komplexität ist es eines der wenigen Module, die ich geschrieben habe, wo die Kommentare im Modul die Menge an Code übersteigen, die geschrieben wurde. 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()

41
2017-08-09 18:49



Rolle einer Metaklasse __call__() Methode beim Erstellen einer Klasseninstanz

Wenn Sie Python-Programmierung für mehr als ein paar Monate gemacht haben, stolpern Sie schließlich über Code, der 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.

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

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

Das __call__() Methode wird aufgerufen, wenn eine Instanz einer Klasse als Callable verwendet wird. Aber wie wir aus früheren Antworten gesehen haben, ist eine Klasse selbst eine Instanz einer Metaklasse. Wenn wir also die Klasse als aufrufbar verwenden (d. H. Wenn wir eine Instanz davon erstellen), rufen wir tatsächlich ihre Metaklasse auf __call__() Methode. An diesem Punkt sind die meisten Python-Programmierer ein wenig verwirrt, weil ihnen das gesagt wurde, als sie eine Instanz wie diese erstellten instance = SomeClass() Du rufst es an __init__() Methode. Einige, die etwas tiefer gegraben haben, wissen das vorher __init__() da ist __new__(). Nun, heute wird eine andere Wahrheitsebene offenbart __new__() Da ist die Metaklasse __call__().

Lassen Sie uns die Aufrufkette der Methode speziell aus der Perspektive des Erstellens einer Instanz einer Klasse betrachten.

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

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.

Der obige Code tut nichts anderes als das Protokollieren der Aufgabe und das Delegieren der tatsächlichen Arbeit an das Eltern (das Halten des Standardverhaltens). Also mit type Sein Meta_1's Elternklasse, können wir uns vorstellen, dass dies die Pseudo-Implementierung 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 Metaklasse ist __call__() Methode ist diejenige, die zuerst genannt wird. Es delegiert dann die Erstellung der Instanz an die Klasse __new__()Methode und Initialisierung zu den Instanzen __init__(). Es ist auch diejenige, die letztendlich die Instanz zurückgibt.

Aus dem Vorstehenden ergibt sich, dass die Metaklasse ist __call__() gibt auch die Möglichkeit zu entscheiden, ob ein Anruf an oder nicht Class_1.__new__() oder Class_1.__init__() wird schließlich gemacht werden. Im Laufe seiner Ausführung könnte es tatsächlich ein Objekt zurückgeben, 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__()."

Lassen Sie uns beobachten, 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

37
2017-12-27 02:21



type ist eigentlich ein metaclass - Eine Klasse, die eine andere Klasse erstellt. Die meisten metaclass sind die Unterklassen von type. Das metaclass erhält die new Klasse als ihr erstes Argument und bieten Zugriff auf Klassenobjekt mit Details 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 zu keiner Zeit instanziiert wurde. der einfache Akt des Erstellens der Klasse löste die Ausführung des metaclass.


27
2017-07-13 07:58