lernen Was sind Metaklassen in Python?



7 Answers

Klassen als Objekte

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

In den meisten Sprachen sind Klassen nur Codeteile, 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, Gegenstände.

Sobald Sie die Schlüsselwortklasse verwenden, führt Python sie aus und erstellt ein OBJECT. 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 erstellen, und deshalb ist es eine Klasse .

Trotzdem ist es ein Objekt und daher:

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

z.B:

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

Klassen dynamisch erstellen

Da Klassen Objekte sind, können Sie sie wie jedes Objekt im Handumdrehen erstellen.

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

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

Wenn Sie das Schlüsselwort class , erstellt Python dieses Objekt automatisch. Aber wie bei den meisten Dingen in Python können Sie dies auch manuell tun.

Erinnern Sie sich an den Funktionstyp? Die gute alte Funktion, mit der Sie wissen können, welchen Typ ein Objekt hat:

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

Nun, type hat eine völlig andere Fähigkeit, es können auch Klassen on the fly erstellt werden. type kann die Beschreibung einer Klasse als Parameter annehmen und eine Klasse zurückgeben.

(Ich weiß, es ist dumm, dass dieselbe Funktion zwei unterschiedliche Verwendungszwecke haben kann, abhängig von den Parametern, die Sie an sie übergeben. Dies ist ein Problem aufgrund der Rückwärtskompatibilitä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 auf diese Weise manuell erstellt werden:

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

Sie werden feststellen, dass wir "MyShinyClass" als Namen der Klasse und als Variable für den Klassenverweis verwenden. Sie können unterschiedlich sein, aber es gibt keinen Grund, die Dinge zu komplizieren.

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

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

Eventuell möchten Sie Ihrer Klasse Methoden hinzufügen. Definieren Sie einfach eine Funktion mit der richtigen Signatur und weisen Sie sie als Attribut zu.

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

Sie können noch mehr Methoden hinzufügen, nachdem Sie die Klasse dynamisch erstellt haben, genau wie das 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 erstellen.

Dies ist, was Python macht, wenn Sie die Schlüsselwortklasse verwenden, und dies mithilfe einer Metaklasse.

Was sind Metaklassen (endlich)

Metaklassen sind das Zeug, das Klassen erstellt.

Sie definieren Klassen, um Objekte zu erstellen, oder?

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

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

MyClass = MetaClass()
my_object = MyClass()

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

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

Dies liegt daran, dass der Funktionstyp tatsächlich eine Metaklasse ist. type ist die Metaklasse, mit der Python alle Klassen hinter den Kulissen erstellt.

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

Nun, ich denke, es ist eine Frage der Konsistenz mit str , der Klasse, die Strings-Objekte erstellt, und int der Klasse, die Ganzzahl-Objekte erstellt. type ist nur die Klasse, die Klassenobjekte erstellt.

Sie sehen das, wenn Sie das Attribut __class__ überprüfen.

Alles und ich meine alles ist ein Gegenstand in Python. Dazu gehören Ints, Strings, Funktionen und Klassen. Alle sind Objekte. Und alle wurden aus einer Klasse erstellt:

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

Was ist nun die __class__ von einer __class__ ?

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

Eine Metaklasse ist also genau das, was Klassenobjekte erstellt.

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

type ist die eingebaute Metaklasse, die Python verwendet, Sie können jedoch selbstverständlich Ihre eigene Metaklasse erstellen.

Das __metaclass__ Attribut

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

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

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

Vorsicht, es ist knifflig.

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

Python __metaclass__ in der Klassendefinition nach __metaclass__ . Wenn es es findet, erstellt es die Objektklasse Foo . Ist dies nicht der Fall, wird die Klasse mit type erstellt.

Lesen Sie das mehrmals.

Wenn Sie das tun:

class Foo(Bar):
    pass

Python macht Folgendes:

Gibt es in Foo ein __metaclass__ Attribut?

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

Wenn Python __metaclass__ nicht finden kann, __metaclass__ es auf MODULE-Ebene nach __metaclass__ und versucht das Gleiche zu tun (jedoch nur für Klassen, die nichts erben, im Grunde alte Klassen).

Wenn es dann keine __metaclass__ finden kann, verwendet es die eigene Metaklasse der Bar (das erste übergeordnete __metaclass__ ) (die möglicherweise der Standardtyp ist), um das Klassenobjekt zu erstellen.

__metaclass__ Sie hierbei darauf, dass das __metaclass__ Attribut nicht vererbt wird, sondern die Metaklasse des übergeordneten Bar.__class__ ( Bar.__class__ ). Wenn Bar ein __metaclass__ Attribut verwendet hat, das Bar mit type() (und nicht type.__new__() ) erstellt hat, erben die Unterklassen dieses Verhalten nicht.

Nun ist die große Frage, was können Sie in __metaclass__ ?

Die Antwort ist: etwas, das eine Klasse schaffen kann.

Und was kann eine Klasse schaffen? type oder alles, was Unterklassen oder verwendet.

Metaklassen in Python 3

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

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

dh das __metaclass__ Attribut wird nicht mehr verwendet, sondern für ein Schlüsselwortargument in der Liste der Basisklassen.

Das Verhalten von Metaklassen bleibt jedoch weitgehend gleich .

Benutzerdefinierte Metaklassen

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

Normalerweise machen Sie dies für APIs, bei denen Sie Klassen erstellen möchten, die dem aktuellen Kontext entsprechen.

Stellen Sie sich ein dummes Beispiel vor, bei dem Sie entscheiden, dass für alle Klassen in Ihrem Modul die Attribute in Großbuchstaben geschrieben werden sollen. Es gibt mehrere Möglichkeiten, dies zu tun, aber eine Möglichkeit besteht darin, __metaclass__ auf Modulebene __metaclass__ .

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

Zum Glück kann __metaclass__ tatsächlich aufrufbar sein, es muss keine formale Klasse sein (ich weiß, etwas mit 'class' im Namen muss keine Klasse sein, go figure ... 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'

Jetzt machen wir genau dasselbe, verwenden aber 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)

Das ist aber nicht wirklich OOP. Wir rufen type direkt an und überschreiben oder nennen das übergeordnete __new__ nicht __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)

Möglicherweise haben Sie das zusätzliche Argument upperattr_metaclass bemerkt. Es ist nichts Besonderes daran: __new__ erhält immer die Klasse, in der sie definiert ist, als ersten Parameter. Genau wie Sie 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 habe, aus Gründen der Klarheit lang, aber wie für sich self haben alle Argumente herkömmliche Namen. Eine echte Produktionsmetaklasse würde also 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 mit super noch sauberer machen, was die Vererbung erleichtert (da ja, Sie können Metaklassen haben, von Metaklassen erben, 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 bei der Verwendung von Metaklassen ist nicht auf Metaklassen zurückzuführen, sondern darauf, dass Sie in der Regel Metaklassen verwenden, um verdrehte __dict__ zu tun, die auf Introspektion beruhen, die Vererbung, vars wie __dict__ usw. beeinflussen.

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

  • eine Klassenerstellung abfangen
  • Ändern Sie die Klasse
  • Die modifizierte Klasse zurückgeben

Warum sollten Sie Metaklassen-Klassen anstelle von Funktionen verwenden?

Da __metaclass__ akzeptieren kann, warum sollten Sie eine Klasse verwenden, da diese offensichtlich komplizierter ist?

Dafür gibt es mehrere Gründe:

  • Die Absicht ist klar. Wenn Sie UpperAttrMetaclass(type) lesen, wissen Sie, was folgen wird
  • Sie können OOP verwenden. Metaklassen können von Metaklassen erben und übergeordnete Methoden überschreiben. Metaklassen können sogar Metaklassen verwenden.
  • Unterklassen einer Klasse sind Instanzen ihrer Metaklasse, wenn Sie eine Metaklassenklasse angegeben haben, jedoch nicht mit einer Metaklassenfunktion.
  • Sie können Ihren Code besser strukturieren. Sie verwenden Metaklassen niemals für etwas so Triviales wie im obigen Beispiel. Normalerweise ist es etwas kompliziertes. Die Möglichkeit, mehrere Methoden zu erstellen und in einer Klasse zu gruppieren, ist sehr hilfreich, um den Code leichter lesbar zu machen.
  • Sie können sich an __new__ , __init__ und __call__ . Damit können Sie verschiedene Sachen machen. Selbst wenn Sie normalerweise alles in __new__ , ist es für manche Leute einfach angenehmer, __init__ .
  • Das nennt man Metaklassen, verdammt noch mal! Es muss etwas bedeuten!

Warum verwenden Sie Metaklassen?

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

Normalerweise tut man das nicht:

Metaklassen sind tiefere Magie, um die sich 99% der Benutzer nie sorgen sollten. Wenn Sie sich fragen, ob Sie sie brauchen, tun Sie das nicht (die Leute, die sie wirklich brauchen, wissen mit Sicherheit, dass sie sie brauchen und brauchen keine Erklärung dafür, warum).

Python Guru Tim Peters

Der Hauptanwendungsfall für eine Metaklasse ist das Erstellen einer API. Ein typisches Beispiel dafür ist der Django ORM.

Sie können so etwas 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 kein IntegerField Objekt zurückgegeben. Es wird ein int und kann sogar direkt aus der Datenbank übernommen werden.

Dies ist möglich, weil models.Model definiert und mit etwas Magie die Person Sie gerade mit einfachen Anweisungen definiert haben, in einen komplexen Hook zu einem Datenbankfeld verwandelt.

Django macht etwas komplexes Aussehen einfach, indem eine einfache API verfügbar gemacht wird und Metaklassen verwendet werden. Dabei wird Code von dieser API neu erstellt, um die eigentliche Arbeit hinter den Kulissen zu erledigen.

Das letzte Wort

Zunächst 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 entweder Instanzen von Klassen oder Instanzen von Metaklassen.

Außer für den type .

type ist eigentlich eine eigene Metaklasse. Dies ist nicht etwas, das Sie in reinem Python reproduzieren könnten. Dies geschieht, indem Sie auf der Implementierungsebene ein wenig schummeln.

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

In 99% der Fälle, in denen Sie die Klasse ändern müssen, können Sie diese besser verwenden.

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

python tutorial english

Was sind Metaklassen und wofür verwenden wir sie?




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

Wenn Sie beispielsweise Django-Modelle betrachten , wirkt deren Definition etwas verwirrend. Es sieht so aus, als würden Sie nur Klasseneigenschaften definieren:

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

Zur Laufzeit werden die Person-Objekte jedoch mit allen möglichen nützlichen Methoden gefüllt. Sehen Sie sich die source für eine erstaunliche Metaklamerei an.




Ich denke, dass die ONLamp-Einführung in die Metaklassen-Programmierung gut geschrieben ist und eine sehr gute Einführung in das Thema gibt, obwohl sie bereits mehrere Jahre alt ist.

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

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

Ich habe selbst noch nie eine geschrieben, aber ich denke, eine der schönsten Anwendungen von Metaklassen kann im Django-Framework gesehen werden . Die Modellklassen verwenden einen Metaklassen-Ansatz, um eine deklarative Schreibweise für neue Modelle oder Formularklassen zu ermöglichen. Während die Metaklasse die Klasse erstellt, haben alle Mitglieder die Möglichkeit, die Klasse selbst anzupassen.

Was Sie noch zu sagen haben: Wenn Sie nicht wissen, was Metaklassen sind, ist die Wahrscheinlichkeit, dass Sie sie nicht benötigen, 99%.




Python 3-Update

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

  • __prepare__ , und
  • __new__

__prepare__können Sie eine benutzerdefinierte Zuordnung (z. B. eine OrderedDict) angeben, die während der Erstellung der Klasse als Namespace verwendet werden soll. Sie müssen eine Instanz des von Ihnen ausgewählten Namespaces zurückgeben. Wenn Sie kein __prepare__Normal implementieren, dictwird verwendet.

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

Eine bloße, nichts-zusätzliche Metaklasse 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 einen einfachen Validierungscode für Ihre Attribute ausführen - als müsste es immer ein intoder ein sein str. Ohne eine Metaklasse würde Ihre Klasse ungefähr so ​​aussehen:

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

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

Eine einfache Metaklasse kann dieses Problem lösen:

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

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

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

Ein 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')

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

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



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

In diesem Fall sah ich Metaclass als Lösung für mein Problem: Ich hatte ein wirklich kompliziertes Problem, das wahrscheinlich anders hätte gelöst werden können, aber ich entschied mich für eine Metaklasse. Aufgrund der Komplexität ist dies eines der wenigen Module, das ich geschrieben habe, bei dem die Kommentare im Modul die Menge an geschriebenem Code überschreiten. Hier ist es...

#!/usr/bin/env python

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

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

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

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

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

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

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

            GsyncListOptions._GsyncListOptions__initialised = True

        def list(cls):
            return GsyncListOptions

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

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

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

            type.__setattr__(GsyncListOptions, name, listvalue)

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

    return GsyncOptionsType

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



Die tl; dr-Version

Die type(obj)Funktion liefert Ihnen den Typ eines Objekts.

Die type()Klasse einer Klasse ist ihre Metaklasse .

So verwenden Sie eine Metaklasse:

class Foo(object):
    __metaclass__ = MyMetaClass



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

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

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

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

type(Hi)
type

type(h)
__main__.Hi

Neben der Verwendung von type () zum dynamischen Erstellen von Klassen können Sie das Erstellungsverhalten von Klassen steuern und die Metaklasse verwenden.

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

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

class CustomList(list, metaclass=ListMetaclass):
    pass

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

lst
['custom_list_1', 'custom_list_2']

Magic wird wirksam, wenn wir Schlüsselwortargumente in Metaklasse übergeben. Es zeigt an, dass der Python-Interpreter die CustomList über ListMetaclass erstellt. Mit new () können Sie zum Beispiel die Klassendefinition ändern, eine neue Methode hinzufügen und die überarbeitete Definition zurückgeben.




Related