oop lernen - Was sind Metaklassen in Python?




tutorial english (13)

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.

Was sind Metaklassen und wofür verwenden wir sie?


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


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

Beachten Sie, dass diese Antwort für Python 2.x gilt, da sie 2008 geschrieben wurde. Die Metaklassen unterscheiden sich in 3.x geringfügig, siehe die Kommentare.

Metaklassen sind die geheime Sauce, die Klasse zum Laufen bringt. Die Standard-Metaklasse für ein neues Stilobjekt wird als "Typ" bezeichnet.

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

Metaklassen nehmen 3 Args. ' Name ', ' Basen ' und ' Dikt '

Hier beginnt das Geheimnis. Suchen Sie in dieser Beispielklassendefinition nach, woher der Name, die Basis und das Diktat stammen.

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

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

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

    return "yellow"

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

print 'TestName = ', repr(TestName)

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

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

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

    return type(name, bases, dict)

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

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

Beachten Sie, dass das magische Verhalten, das "Initalized" durch die Metaklasse "init_attributes" erzielt, nicht an eine Unterklasse von "Initalized" übergeben wird.

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

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

 class Foo(object):
     __metaclass__ = MetaSingleton

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

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.


Zusätzlich zu den veröffentlichten Antworten kann ich sagen, dass a metaclassdas Verhalten einer Klasse definiert. Sie können also Ihre Metaklasse explizit festlegen. Immer wenn Python ein Schlüsselwort erhält, classsucht es nach dem Schlüsselwort metaclass. Wenn es nicht gefunden wird, wird der Standard-Metaklassentyp zum Erstellen des Klassenobjekts verwendet. Mit dem __metaclass__Attribut können Sie metaclassIhre Klasse festlegen :

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

print(MyClass.__metaclass__)

Es wird die Ausgabe wie folgt erzeugen:

class 'type'

Natürlich können Sie auch eigene erstellen metaclass, um das Verhalten jeder Klasse zu definieren, die mit Ihrer Klasse erstellt wird.

Dazu muss Ihre Standardtypklasse metaclassvererbt werden, da dies die Hauptkategorie ist metaclass:

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

class MyTestClass:
   __metaclass__ = MyMetaClass

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

Die Ausgabe wird sein:

class '__main__.MyMetaClass'
class 'type'

Rolle einer Metaklassenmethode __call__()beim Erstellen einer Klasseninstanz

Wenn Sie Python für mehr als ein paar Monate programmiert haben, stolpern Sie ü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 in der Klasse implementieren .

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

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

Die __call__()Methode wird aufgerufen, wenn eine Instanz einer Klasse als Callable verwendet wird. Wie wir jedoch in vorherigen Antworten gesehen haben, ist eine Klasse selbst eine Instanz einer Metaklasse. Wenn wir also die Klasse als aufrufbare Klasse verwenden (dh, wenn wir eine Instanz davon erstellen), nennen wir sie eigentlich ihre Metaklass- __call__()Methode. Zu diesem Zeitpunkt sind die meisten Python-Programmierer etwas verwirrt, weil ihnen gesagt wurde, dass sie beim Erstellen einer Instanz wie dieser instance = SomeClass()ihre __init__()Methode aufrufen . Einige, die ein bisschen tiefer gegraben haben, wissen das, bevor es __init__()da ist __new__(). Nun, heute wird eine andere Wahrheitsebene offenbart, bevor __new__()es die Metaklasse gibt __call__().

Untersuchen wir die Aufrufkette der Methode speziell unter dem Aspekt, eine Instanz einer Klasse zu erstellen.

Dies ist eine Metaklasse, die sich genau in dem 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.

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

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

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

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

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

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

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

        # then we return it
        return instance

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

Aus dem Vorstehenden ergibt sich, dass der Metaklasse __call__()auch die Möglichkeit gegeben wird, zu entscheiden, ob ein Aufruf an Class_1.__new__()oder nicht erfolgen Class_1.__init__()wird oder nicht . Im Verlauf 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 Sie wiederholt versuchen, 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

Andere haben erklärt, wie Metaklassen funktionieren und wie sie in das Python-Typensystem passen. Hier ist ein Beispiel, wofür sie verwendet werden können. In einem Testrahmen, den ich geschrieben habe, wollte ich die Reihenfolge verfolgen, in der Klassen definiert wurden, damit ich sie später in dieser Reihenfolge instanziieren konnte. 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, erhält ein Klassenattribut _order , das die Reihenfolge aufzeichnet, in der die Klassen definiert wurden.


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.


Python-Klassen sind selbst - wie in der Instanz - Objekte ihrer Meta-Klasse.

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

class foo:
    ...

Metaklassen werden verwendet, um einige Regeln auf eine ganze Gruppe von Klassen anzuwenden. Angenommen, Sie erstellen ein ORM, um auf eine Datenbank zuzugreifen, und Sie möchten, dass Datensätze aus jeder Tabelle einer Klasse zugeordnet sind, die dieser Tabelle zugeordnet ist (basierend auf Feldern, Geschäftsregeln usw.). Dies ist eine mögliche Verwendung der Metaklasse ist beispielsweise die Verbindungspoollogik, die von allen Datensatzklassen aus allen Tabellen gemeinsam genutzt wird. Eine andere Verwendung ist die Logik, um Fremdschlüssel zu unterstützen, die mehrere Klassen von Datensätzen umfassen.

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

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

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

  in the following case:

  class foo(baseclass):
        __metaclass__ = somemeta

  an_attr = 12

  def bar(self):
      ...

  @classmethod
  def foo(cls):
      ...

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

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


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


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

        call order :

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

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

Jedenfalls sind diese beiden am häufigsten verwendeten Haken. Metaklassierung ist mächtig, und darüber hinaus gibt es bei weitem keine erschöpfende Liste von Verwendungen für Metaklassierung.


Was sind Metaklassen? Wofür benutzt du sie?

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

Pseudocode:

>>> Class(...)
instance

Das oben genannte sollte bekannt aussehen. Nun, wo kommt die Class ? Es ist eine Instanz einer Metaklasse (auch Pseudocode):

>>> Metaclass(...)
Class

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

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

Anders ausgedrückt

  • Eine Klasse ist 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 Standard-Metaklasse type , wird sie instanziiert:

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

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

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

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

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

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

Die möglichen Verwendungen von Metaklassen sind grenzenlos. Einige Ideen, die erforscht wurden, umfassen Protokollierung, Schnittstellenprüfung, automatische Delegierung, automatische Erstellung von Eigenschaften, Proxies, Frameworks und automatische Sperrung / Synchronisierung von Ressourcen.

Nichtsdestotrotz wird der Benutzer in der Regel dazu angehalten, die Verwendung von Metaklassen zu vermeiden, sofern dies nicht unbedingt erforderlich ist.

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

Wenn Sie zum Beispiel eine Klassendefinition schreiben,

class Foo(object): 
    'demo'

Sie instanziieren ein Klassenobjekt.

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

Es entspricht dem funktional aufrufenden type mit den entsprechenden Argumenten und der Zuordnung 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 zum __dict__ hinzugefügt werden, dh zum Namespace:

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

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

(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 __slots__ nicht definieren - wenn wir __slots__ definieren, __slots__ wir ein bisschen Platz in den Instanzen, da wir __dict__ und __weakref__ durch Ausschluss von ihnen verbieten können, zum Beispiel:

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

... Aber ich schweife ab.)

Wir können type wie jede andere Klassendefinition erweitern:

Hier ist die Standardeinstellung 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 __repr__ . Wenn wir help(repr) aufrufen help(repr) erfahren wir, dass es einen guten Test für __repr__ , der auch einen Test auf Gleichheit erfordert - obj == eval(repr(obj)) . Die folgende einfache Implementierung von __repr__ und __eq__ für Klasseninstanzen unseres Typs class bietet eine Demonstration, die die Standardwerte von __repr__ von Klassen verbessern kann:

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

Wenn wir nun ein Objekt mit dieser Metaklasse erstellen, bietet das __repr__Echo in der Befehlszeile einen viel weniger hässlichen Anblick als 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 __repr__für die Klasseninstanz definierten Code haben wir eine stärkere Möglichkeit, unseren Code zu debuggen. Eine weitere Überprüfung eval(repr(Class))ist jedoch unwahrscheinlich (da Funktionen normalerweise nicht von ihren Standardwerten abgewälzt werden könnten __repr__).

Eine erwartete Verwendung: __prepare__ein Namespace

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

from collections import OrderedDict

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

Und Verwendung:

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

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

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

Beachten Sie, dass dieses Beispiel aus der docs angepasst wurde - die neue Enumeration in der Standardbibliothek macht dies.

Also haben wir eine Metaklasse durch Erstellen einer Klasse instanziiert. Wir können die Metaklasse auch wie jede andere Klasse behandeln. Es hat eine Methodenauflösungsreihenfolge:

>>> 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 auswerten können, wenn wir keinen Weg finden, unsere Funktionen darzustellen).

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

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

Vor kurzem bin ich darauf gestoßen, als ich eine Klasse in Python (Deep Learning) erstellt habe. Nach meinem Verständnis und in Übereinstimmung mit den oben aufgeführten Erklärungen werde ich darauf eingehen

__name__=="__main__"

Manchmal konstruieren wir eine Klasse in einer .py-Datei und definieren viele Funktionen innerhalb dieser Klasse. Wir möchten jedoch nicht alle diese Klassenfunktionen für einen einzigen Zweck verarbeiten. Zum Beispiel zum Erstellen einer Klasse und zum Definieren einiger Funktionen für die Datenerstellung (.npy-Dateien) und zum Laden von Daten. also wenn wir definieren

__name__=="__main__"

xxx = Klassenname ()

xxx.create_data ()

Bedeutet, wenn wir die .py-Datei aufrufen, werden nur Daten erstellt und keine anderen Klassenfunktionen verarbeitet. Andere Klassenfunktionen (Datenladen) können auch von anderen Klassen importiert werden.





python oop metaclass python-datamodel