Was sind Metaklassen in Python?

Was sind Metaklassen und wofür verwenden wir sie?

   

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

Metaklassen-Diagramm

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

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

Wenn die class ausgeführt wird, führt Python zuerst den Hauptteil der class als normalen Codeblock aus. Der resultierende Namespace (ein Dict) enthält die Attribute der zukünftigen class. Die Metaklasse wird durch Betrachten der Basisklassen der zu bearbeitenden class (Metaklassen werden vererbt), des Attributs __metaclass__ der zu __metaclass__ class (falls vorhanden) oder der globalen Variable __metaclass__ . Die Metaklasse wird dann mit dem Namen, den Basen und Attributen der class aufgerufen, um sie zu instanziieren.

Metaklassen definieren jedoch tatsächlich den Typ einer class und nicht nur eine Fabrik dafür, sodass Sie mit ihnen viel mehr tun können. Sie können zum Beispiel normale Methoden für die Metaklasse definieren. Diese Metaklassen-Methoden sind wie classnmethoden, da sie für die class ohne eine Instanz aufgerufen werden können, aber sie sind auch nicht wie classnmethoden, da sie für eine Instanz der class nicht aufgerufen werden können. type.__subclasses__() ist ein Beispiel für eine Methode zum type metaclass. Sie können auch die normalen magischen Methoden wie __add__ , __iter__ und __getattr__ , um das Verhalten der class zu implementieren oder zu ändern.

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__ 

classn als Objekte

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

In den meisten Sprachen sind classn 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 classn sind mehr als das in Python. classn sind auch Objekte.

Ja, Objekte.

Sobald Sie die Schlüsselwortklasse verwenden, führt Python sie aus und erstellt ein OBJEKT. Die Anleitung

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

erstellt im Speicher ein Objekt mit dem Namen “ObjectCreator”.

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

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 functionsparameter übergeben

z.B:

 >>> print(ObjectCreator) # you can print a class because it's an object  >>> def echo(o): ... print(o) ... >>> echo(ObjectCreator) # you can pass a class as a parameter  >>> 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> 

classn dynamisch erstellen

Da classn Objekte sind, können Sie sie wie jedes andere Objekt erstellen.

Zuerst können Sie eine class in einer function mit der 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  >>> 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 class noch selbst schreiben musst.

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

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

Merken Sie sich den functionstyp? Die gute alte function, die Sie wissen lässt, um welchen Typ es sich bei einem Objekt handelt:

 >>> print(type(1))  >>> print(type("1"))  >>> print(type(ObjectCreator))  >>> print(type(ObjectCreator()))  

Nun, type hat eine völlig andere Fähigkeit, es kann auch classn im laufenden Betrieb erstellen. type kann die Beschreibung einer class als Parameter annehmen und eine class zurückgeben.

(Ich weiß, es ist albern, dass die gleiche function zwei völlig unterschiedliche Anwendungen haben kann, je nach den Parametern, die Sie übergeben. Es ist ein Problem aufgrund der Abwärtskompatibilität in Python.)

type funktioniert auf diese Weise:

 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)  >>> print(MyShinyClass()) # create an instance with the class <__main__ .MyShinyClass object at 0x8997cec> 

Sie werden feststellen, dass wir “MyShinyClass” als Namen der class und als Variable für die classnreferenz verwenden. Sie können unterschiedlich sein, aber es gibt keinen Grund, Dinge zu komplizieren.

type akzeptiert ein Wörterbuch, um die Attribute der class zu definieren. Damit:

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

Kann übersetzt werden in:

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

Und als normale class verwendet:

 >>> print(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)  >>> print(FooChild.bar) # bar is inherited from Foo True 

Schließlich möchten Sie Ihrer class Methoden hinzufügen. Definieren Sie einfach eine function mit der richtigen Signatur und weisen Sie sie als Attribut zu.

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

Und Sie können sogar noch weitere Methoden hinzufügen, nachdem Sie die class dynamisch erstellt haben, genau wie beim Hinzufügen von Methoden zu einem normalerweise erstellten classnobjekt.

 >>> 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 classn Objekte, und Sie können dynamisch eine class dynamisch erstellen.

Das macht Python, wenn Sie die Schlüsselwortklasse verwenden, und zwar mithilfe einer Metaklasse.

Was sind Metaklassen (endlich)

Metaklassen sind das Zeug, das classn schafft.

Sie definieren classn, um Objekte zu erstellen, oder?

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

Nun, Metaklassen schaffen diese Objekte. Sie sind die classn der classn, man kann sie sich so vorstellen:

 MyClass = MetaClass() my_object = MyClass() 

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

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

Das liegt daran, dass der functionstyp tatsächlich eine Metaklasse ist. type ist die Metaklasse, mit der Python alle classn hinter den Kulissen erstellt.

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

Nun, ich denke, es ist eine Frage der Konsistenz mit str , der class, die Strings-Objekte erzeugt, und int der class, die Integer-Objekte erzeugt. type ist nur die class, die classnobjekte erstellt.

Das sehen Sie, indem Sie das Attribut __class__ überprüfen.

Alles, und ich meine alles, ist ein Objekt in Python. Dazu gehören Ints, Strings, functionen und classn. Sie alle sind Objekte. Und alle wurden aus einer class erstellt:

 >>> age = 35 >>> age.__class__  >>> name = 'bob' >>> name.__class__  >>> def foo(): pass >>> foo.__class__  >>> class Bar(object): pass >>> b = Bar() >>> b.__class__  

Was ist nun die __class__ eines __class__ ?

 >>> age.__class__.__class__  >>> name.__class__.__class__  >>> foo.__class__.__class__  >>> b.__class__.__class__  

Eine Metaklasse ist also nur der Stoff, der classnobjekte erzeugt.

Sie können es eine “classnfabrik” nennen, wenn Sie es wünschen.

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

Das __metaclass__ Attribut

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

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

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

Vorsicht, es ist schwierig.

Sie schreiben zuerst die class Foo(object) , aber das classnobjekt Foo wird noch nicht im Speicher angelegt.

Python sucht nach __metaclass__ in der classndefinition. Wenn es gefunden wird, wird es verwendet, um die Objektklasse Foo zu erzeugen. Wenn dies nicht der Fall ist, wird type , um die class zu erstellen.

Lesen Sie das mehrmals.

Wenn Sie das tun:

 class Foo(Bar): pass 

Python macht folgendes:

Gibt es in Foo ein Attribut __metaclass__ ?

Wenn ja, erstelle im Speicher ein classnobjekt (ich sagte ein classnobjekt, bleib hier bei mir) mit dem Namen Foo indem ich benutze, was in __metaclass__ .

Wenn Python __metaclass__ nicht finden kann, __metaclass__ es auf MODUL-Ebene nach __metaclass__ und versucht, dasselbe zu tun (aber nur für classn, die nichts erben, im Grunde alte classn).

Wenn es dann überhaupt keine __metaclass__ finden kann, wird es die eigene Metaklasse des __metaclass__ (die erste übergeordnete) verwenden (die der Standardtyp sein könnte), um das classnobjekt zu erzeugen.

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

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

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

Und was kann eine class schaffen? type oder alles, was es unterklassifiziert oder benutzt.

Benutzerdefinierte Metaklassen

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

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

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

Auf diese Weise werden alle classn 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 class sein (ich weiß, etwas mit “class” in seinem Namen muss keine class sein, geh Figur … aber es ist hilfreich).

Wir beginnen mit einem einfachen Beispiel, indem wir eine function 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 class 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 rufen den type direkt auf, und wir überschreiben oder rufen das Elternelement __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 gibt nichts besonderes daran: __new__ erhält immer die class, in der es definiert ist, als ersten Parameter. Genauso wie Sie self für gewöhnliche Methoden haben, die die Instanz als ersten Parameter oder die definierende class für classnmethoden erhalten.

Natürlich sind die Namen, die ich hier verwendete, der Deutlichkeit halber lang, aber wie für mich self haben alle Argumente konventionelle 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 sogar noch sauberer machen, indem wir super , was die inheritance erleichtert (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 an Metaklassen, sondern daran, dass Sie normalerweise Metaklassen verwenden, um verdrehte __dict__ zu tun, die auf Introspektion, Manipulation der inheritance, Variablen wie __dict__ usw. __dict__ .

In der Tat sind Metaklassen besonders nützlich, um schwarze Magie und damit komplizierte Dinge zu tun. Aber für sich sind sie einfach:

  • Abfangen einer classnerstellung
  • Modifizieren Sie die class
  • Gib die modifizierte class zurück

Warum würden Sie Metaklassen-classn anstelle von functionen verwenden?

Da __metaclass__ aufrufbar ist, warum sollten Sie eine class verwenden, da sie offensichtlich komplizierter ist?

Dafür gibt es mehrere Gründe:

  • Die Absicht ist klar. Wenn Sie UpperAttrMetaclass(type) lesen, wissen Sie, was folgt
  • Sie können OOP verwenden. Metaklasse kann von Metaklasse erben, überschreiben übergeordnete Methoden. Metaklassen können sogar Metaklassen verwenden.
  • Unterklassen einer class werden Instanzen ihrer Metaklasse sein, wenn Sie eine Metaklasse-class, aber nicht eine Metaklassen-function angegeben haben.
  • Sie können Ihren Code besser strukturieren. Sie verwenden Metaklassen nie für etwas so Triviales wie das obige Beispiel. Es ist normalerweise etwas Kompliziertes. Die Fähigkeit, mehrere Methoden zu erstellen und sie in einer class zu gruppieren, ist sehr nützlich, um den Code leichter lesbar zu machen.
  • Sie können auf __new__ , __init__ und __call__ . Was dir erlauben wird, verschiedene Sachen zu machen. Selbst wenn Sie normalerweise alles in __new__ , sind einige Leute einfach bequemer mit __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 errorsanfälliges Feature verwenden?

Naja, normalerweise tust du nicht:

Metaklassen sind eine tiefere Magie, über die sich 99% der Nutzer keine Sorgen machen sollten. Wenn du dich fragst, ob du sie brauchst, tust du es nicht (die Leute, die sie wirklich brauchen, wissen mit Sicherheit, dass sie sie brauchen und brauchen keine Erklärung 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 kein IntegerField Objekt zurückgegeben. Es gibt ein int und kann es sogar direkt aus der database übernehmen.

Dies ist möglich, weil models.Model definiert und eine gewisse Magie verwendet, die die Person Sie gerade mit einfachen statementen definiert haben, in einen komplexen Hook in ein databasefeld verwandelt.

Django macht etwas Komplexes einfach, indem es eine einfache API bereitstellt und Metaklassen verwendet, um Code aus dieser API neu zu erstellen, um die eigentliche Arbeit hinter den Kulissen zu leisten.

Das letzte Wort

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

Tatsächlich sind classn selbst Instanzen. Von Metaklassen.

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

Alles ist ein Objekt in Python und alle sind entweder Instanzen von classn oder Instanzen von Metaklassen.

Außer für den type .

type ist eigentlich eine eigene Metaklasse. Dies ist nichts, was Sie in reinem Python reproduzieren könnten, und es wird getan, indem Sie ein wenig auf der Implementierungsebene betrügen.

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

  • Affenflicken
  • class Dekorateure

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

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

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 “class” 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 ‘ Diktat

Hier beginnt das Geheimnis. Suchen Sie in dieser beispielhaften classndefinition 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 ‘ 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 (, None, , 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 class 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 init_attributes erhält, nicht an eine Unterklasse von Initalised weitergegeben wird.

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

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

Wenn Sie sich beispielsweise Django-Modelle ansehen, sieht ihre Definition etwas verwirrend aus. Es sieht so aus, als ob Sie nur classneigenschaften 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.

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 classn 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 ist, erhält dann ein classnattribut _order , das die Reihenfolge aufzeichnet, in der die classn definiert wurden.

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 (archiviert unter https://web.archive.org/web/20080206005253/http://www.onlamp. com / pub / a / python / 2003/04/17 / metaklassen.html )

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

Ich habe selbst nie einen selbst geschrieben, aber ich denke, einer der schönsten Einsatzbereiche von Metaklassen ist im Django-Framework zu sehen . Die Modellklassen verwenden einen Metaklassenansatz, um einen deklarativen Stil zum Schreiben neuer Modelle oder Formularklassen zu ermöglichen. Während die Metaklasse die class erstellt, haben alle Mitglieder die Möglichkeit, die class selbst anzupassen.

  • Erstellen eines neuen Modells
  • Die Metaklasse, die dies ermöglicht

Die Sache, die noch zu sagen ist, ist: Wenn Sie nicht wissen, was Metaklassen sind, ist die Wahrscheinlichkeit, dass Sie sie nicht brauchen, 99%.

Was sind Metaklassen? Wofür benutzt du sie?

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

Pseudocode:

 >>> Class(...) instance 

Das obige sollte Ihnen bekannt vorkommen. Woher kommt die Class ? Es ist eine Instanz einer Metaklasse (auch Pseudocode):

 >>> Metaclass(...) Class 

In echtem Code können wir die Standard-Metaklasse übergeben, geben Sie alles ein, was wir benötigen, um eine class zu instanziieren, und wir erhalten eine class:

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

Anders ausgedrückt

  • Eine class ist für eine Instanz wie eine Metaklasse eine class.

    Wenn wir ein Objekt instanziieren, erhalten wir eine Instanz:

     >>> object() # instantiation of class  # instance 

    Wenn wir eine class explizit mit der Standardmetaklasse definieren, geben wir sie instanziiert ein:

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

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

     >>> type(object) == type True >>> object.__class__  

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

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

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

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 class erstellen:

Wenn Sie beispielsweise eine classndefinition schreiben,

 class Foo(object): 'demo' 

Sie instanziieren ein classnobjekt.

 >>> Foo  >>> isinstance(Foo, type), isinstance(Foo, object) (True, True) 

Dies ist der gleiche functionsaufruf von type mit den entsprechenden Argumenten und das Zuordnen 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__ , dh dem Namespace, hinzugefügt werden:

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

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

(Eine __dict__ zum Inhalt der class __dict__ : __module__ ist da, weil classn wissen müssen, wo sie definiert sind, und __dict__ und __weakref__ sind da, weil wir __slots__ nicht definieren – wenn wir __slots__ definieren, __slots__ wir etwas davon Leerzeichen in den Instanzen, da wir __dict__ und __weakref__ nicht __dict__ können, __dict__ __weakref__ ausschließen. Beispiel:

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

… Aber ich schweife ab.)

Wir können den type wie jede andere classndefinition erweitern:

Hier ist der Standard __repr__ der classn:

 >>> Foo  

Eines der wertvollsten Dinge, die wir standardmäßig beim Schreiben eines Python-Objekts tun können, besteht darin, es mit einem guten __repr__ . Wenn wir help(repr) aufrufen help(repr) erfahren wir, dass es einen guten Test für einen __repr__ , der auch einen Test auf Gleichheit erfordert – obj == eval(repr(obj)) . Die folgende einfache Implementierung von __repr__ und __eq__ für classninstanzen unserer Typklasse liefert eine Demonstration, die den Standard __repr__ von classn 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 __repr__ , bietet das __repr__ in der Befehlszeile einen wesentlich 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 netten __repr__ , der für die __repr__ definiert ist, können wir unseren Code __repr__ debuggen. However, much further checking with eval(repr(Class)) is unlikely (as functions would be rather impossible to eval from their default __repr__ ‘s).

An expected usage: __prepare__ a namespace

If, for example, we want to know in what order a class’s methods are created in, we could provide an ordered dict as the namespace of the class. We would do this with __prepare__ which returns the namespace dict for the class if it is implemented in Python 3 :

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

And usage:

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

And now we have a record of the order in which these methods (and other class attributes) were created:

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

Note, this example was adapted from the documentation – the new enum in the standard library does this.

So what we did was instantiate a metaclass by creating a class. We can also treat the metaclass as we would any other class. It has a method resolution order:

 >>> inspect.getmro(OrderedType) (, , , ) 

And it has approximately the correct repr (which we can no longer eval unless we can find a way to represent our functions.):

 >>> OrderedMethodsObject OrderedType('OrderedMethodsObject', (object,), {'method1': , 'members': ('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4'), 'method3': , 'method2': , '__module__': '__main__', '__weakref__': , '__doc__': None, '__d ict__': , 'method4': }) 

Python 3 update

There are (at this point) two key methods in a metaclass:

  • __prepare__ , and
  • __new__

__prepare__ lets you supply a custom mapping (such as an OrderedDict ) to be used as the namespace while the class is being created. You must return an instance of whatever namespace you choose. If you don’t implement __prepare__ a normal dict is used.

__new__ is responsible for the actual creation/modification of the final class.

A bare-bones, do-nothing-extra metaclass would like:

 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:

Say you want some simple validation code to run on your attributes — like it must always be an int or a str . Without a metaclass, your class would look something like:

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

As you can see, you have to repeat the name of the attribute twice. This makes typos possible along with irritating bugs.

A simple metaclass can address that problem:

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

This is what the metaclass would look like (not using __prepare__ since it is not needed):

 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) 

A sample run of:

 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  p.weight = '9' File "simple_meta.py", line 24, in __set__ (self.name, self.type, value)) TypeError: weight must be of type(s)  (got '9') 

Note : This example is simple enough it could have also been accomplished with a class decorator, but presumably an actual metaclass would be doing much more.

The ‘ValidateType’ class for reference:

 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 

A metaclass is a class that tells how (some) other class should be created.

This is a case where I saw metaclass as a solution to my problem: I had a really complicated problem, that probably could have been solved differently, but I chose to solve it using a metaclass. Because of the complexity, it is one of the few modules I have written where the comments in the module surpass the amount of code that has been written. Here it is…

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

Role of a metaclass’ __call__() method when creating a class instance

If you’ve done Python programming for more than a few months you’ll eventually stumble upon code that looks like this:

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

The latter is possible when you implement the __call__() magic method on the class.

 class SomeClass(object): # ... # some definition here ... # ... def __call__(self, foo, bar): return bar + foo 

The __call__() method is invoked when an instance of a class is used as a callable. But as we’ve seen from previous answers a class itself is an instance of a metaclass, so when we use the class as a callable (ie when we create an instance of it) we’re actually calling its metaclass’ __call__() method. At this point most Python programmers are a bit confused because they’ve been told that when creating an instance like this instance = SomeClass() you’re calling its __init__() method. Some who’ve dug a bit deeper know that before __init__() there’s __new__() . Well, today another layer of truth is being revealed, before __new__() there’s the metaclass’ __call__() .

Let’s study the method call chain from specifically the perspective of creating an instance of a class.

This is a metaclass that logs exactly the moment before an instance is created and the moment it’s about to return it.

 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 

This is a class that uses that metaclass

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

And now let’s create an instance of Class_1

 instance = Class_1() # Meta_1.__call__() before creating an instance of . # 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. 

Observe that the code above doesn’t actually do anything more than logging the tasks. Each method delegates the actual work to its parent’s implementation, thus keeping the default behavior. Since type is Meta_1 ‘s parent class ( type being the default parent metaclass) and considering the ordering sequence of the output above, we now have a clue as to what would be the pseudo implementation of 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 

We can see that the metaclass’ __call__() method is the one that’s called first. It then delegates creation of the instance to the class’s __new__() method and initialization to the instance’s __init__() . It’s also the one that ultimately returns the instance.

From the above it stems that the metaclass’ __call__() is also given the opportunity to decide whether or not a call to Class_1.__new__() or Class_1.__init__() will eventually be made. Over the course of its execution it could actually return an object that hasn’t been touched by either of these methods. Take for example this approach to the singleton pattern:

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

Let’s observe what happens when repeatedly trying to create an object of type 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() #  singleton returning from Meta_2.__call__(), skipping creation of new instance. c = Class_2() #  singleton returning from Meta_2.__call__(), skipping creation of new instance. a is b is c # True 

type is actually a metaclass — a class that creates another classes. Most metaclass are the subclasses of type . The metaclass receives the new class as its first argument and provide access to class object with details as mentioned below:

 >>> 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 : Defining class  get_choch :'dairy' __module__ :'builtins' __qualname__ :'NewClass' 

Note:

Notice that the class was not instantiated at any time; the simple act of creating the class triggered execution of the metaclass .

The tl;dr version

The type(obj) function gets you the type of an object.

The type() of a class is its metaclass .

To use a metaclass:

 class Foo(object): __metaclass__ = MyMetaClass 

Python classes are themselves objects – as in instance – of their meta-class.

The default metaclass, which is applied when when you determine classes as:

 class foo: ... 

meta class are used to apply some rule to an entire set of classes. For example, suppose you’re building an ORM to access a database, and you want records from each table to be of a class mapped to that table (based on fields, business rules, etc..,), a possible use of metaclass is for instance, connection pool logic, which is share by all classes of record from all tables. Another use is logic to to support foreign keys, which involves multiple classes of records.

when you define metaclass, you subclass type, and can overrided the following magic methods to insert your logic.

 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": , "foo": } 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 

anyhow, those two are the most commonly used hooks. metaclassing is powerful, and above is nowhere near and exhaustive list of uses for metaclassing.

The type() function can return the type of an object or create a new type,

for example, we can create a Hi class with the type() function and do not need to use this way with class Hi(object):

 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 

In addition to using type() to create classes dynamically, you can control creation behavior of class and use metaclass.

According to the Python object model, the class is the object, so the class must be an instance of another certain class. By default, a Python class is instance of the type class. That is, type is metaclass of most of the built-in classes and metaclass of user-defined classes.

 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 will take effect when we passed keyword arguments in metaclass, it indicates the Python interpreter to create the CustomList through ListMetaclass. new (), at this point, we can modify the class definition, for example, and add a new method and then return the revised definition.

Two sentences to master Python’s most difficult knowledge point: Metaclass

Original source: segmentfault.com/a/1190000011447445

translated and corrected by me.

The orignal author of this article preserve all right, however the translating jobs still can not be ignored

If there are some mistakes or some format against PEP8, please help me to correct it. Vielen Dank!!!

At the begining, there are some examples from chinese traditional culture(I am not Chinese, but I have some knowledge about it. If you like it, it is good. And if you don’t, just ignore it. The understanding of metaclass is most important thing)

It is a brief introduction of Metaclass in Python with some practical and useful example. Wish you will like it.

Don’t be frightened by such rhetoric as the so-called “feature that the metaclass is not used by 99% of Python programmers.” Because every person is a natural user.

To understand metaclasses, you only need to know two sentences:

sentence 1: one came from truth, two came from one, three came from two, all the things came from three

sentence 2: who am I? Where did I come from? Where do I go?

In the python world, there is an eternal truth, that is, “type”, please remember in mind, type is the truth. The python ecosystem that is so vast is produced by type.

one came from truth, two came from one, three came from two, all the things came from three:

The truth is type

One is the metaclass (metaclass, or class generator)

Second is the class (class, or instance generator)

Three is an instance (example)

Everything is the various attributes and methods of an instance. When we use Python, we call them.

Truth and One are the propositions we discuss today. The second, third, and all things are the classes, instances, attributes, and methods that we often use. We use hello world as an example:

 # Create a Hello class that has the attributes say_hello ---- Second Origin class Hello () : def say_hello ( self ,name= 'world' ) : print( 'Hello, %s.' % name ) # Create an instance hello from the Hello class ---- Two students three Hello = Hello () # Use hello to call the method say_hello ---- all three things Hello.say_hello () 

Output effect:

 Hello, world. 

This is a standard “three came from two, all the things came from three” process. From the class to the methods we can call, these two steps are used.

Then we can’t help from the main question, where does the class come from? Go back to the first line of code.

The class Hello is actually a “semantic abbreviation” of a function, just to make the code easier to understand. Another way of writing it is:

 def fn(self ,name='world' ) : # If we have a function called fn print ( 'Hello, %s.' % name ) Hello = type ('Hello',(object,),dict(say_hello = fn)) # Create Hello class by type ---- Mysterious "Truth", you can change everything, this time we directly from the "Truth" gave birth to "2" 

This type of writing is exactly the same as the previous Class Hello writing. You can try to create an instance and call it.

 # Create an instance of hello from the Hello class. hello = Hello () # Use hello call method say_hello ---- all three things, exactly the same Hello . say_hello () 

Output effect:

 Hello, world. ---- The result of the call is exactly the same. 

We looked back at the most exciting place. The road gave birth directly to two:

 Hello = type('Hello', (object,), dict(say_hello=fn)) 

This is the “Truth”, the origin of the python world. You can marvel at this.

Pay attention to its three parameters! Three eternal propositions that coincide with mankind: Who am I, where do I come from, where do I go?

 The first parameter: who I am. Here, I need a name that distinguishes everything else. The above example names me "Hello." The second parameter: where do I come from. Here, I need to know where I come from, which is my "parent". In my example above, my parent is "object" - a very primitive class in Python. The third parameter: Where do I go? Here, we include the methods and properties that we need to call into a dictionary and pass them as parameters. In the above example, we have a say_hello method packed into a dictionary. 

It is worth noting that the three major eternal propositions are all classes, all instances, and even all instance properties and methods. As it should be, their “creators”, Truth and One, namely type and metaclass, also have these three parameters. But usually, the three eternal propositions of the class are not passed as parameters, but are passed in as follows

 class Hello(object):{ After class # statement "Who Am I?" # In the parentheses declare "where do I come from" # In curly brackets declare "Where do I go?" def say_hello ():{ } } The Creator can create a single person directly, but this is a hard labor. The Creator will first create the species "human" and then create a specific individual in batches. And pass on the three eternal propositions. "Truth" can produce "2" directly, but it will produce "1" and then make "2" in batches. Type can directly generate a class, but it can also be a metaclass and then use a metaclass to customize a class. 

Metaclass – One came from Truth, two came from one.

In general, metaclasses are named suffix Metaclass. Imagine that we need a metaclass that can automatically say hello. The class methods in it, sometimes need say_Hello, sometimes say_Hi, sometimes say_Sayolala, and sometimes say_Nihao.

If every built-in say_xxx needs to be declared once in a class, how terribly hard work it will be! It is better to use metaclasses to solve the problem.

The following is a meta class code for creating a special “greet”:

 class SayMetaClass(type): def __new__ (cls, Name ,Bases ,Attrs) : attrs[ 'say_' + name ] = lambda self, value , saying = name : print ( saying + ',' + value + '!' ) Return Type . __new__ ( cls ,name, bases , attrs) 

Remember two things:

 Metaclasses are derived from "type", so the parent class needs to pass in the type. [Taosheng 1, so one must include Tao] Metaclass operations are done in __new__. The first parameter is the class that will be created. The following parameters are the three eternal propositions: Who am I, where do I come from, and where do I go. The objects it returns are also the three eternal propositions. Next, these three parameters will always be with us. 

In new , I only performed one operation.

 Attrs['say_'+name] = lambda self,value,saying=name: print(saying+','+value+'!') 

It creates a class method with the name of the class. For example, the class we created from metaclass is called “Hello”. When it was created, it would automatically have a class method called “say_Hello”. Then it would use the class name “Hello” as the default parameter to say, and passed it to the method. Then pass in the hello method call as a value, and finally print it out.

So how does a metaclass go from creation to invocation?

Come! Together with the principles of Daosheng, Yishengyou, Bishengsan, Sanshengwu, enter the life cycle of the Yuan class!

 # Tao Shengyi: incoming type class SayMetaClass(type): # Incoming three eternal propositions: class name, parent class, attribute def __new__(cls ,name ,bases ,attrs): # Create "talent" attrs[ 'say_' + name ] = lambda self, value , saying = name : print( saying + ',' + value + '!' ) # Three eternal propositions: class name, parent class, attribute return type . __new__ ( cls ,name ,bases ,attrs ) # Lifetime 2: Create class class Hello ( object ,metaclass = SayMetaClass): pass # two students three: create a real column Hello = Hello () # Three things: call the instance method hello.say_Hello('world!') 

The output is

 Hello, world! 

Note: The class created by the metaclass, the first parameter is the parent class, the second parameter is the metaclass

Ordinary people will not be able to speak at birth, but some people will say hello, “hello” and “sayolala” when they are born. This is the power of talent. It will give us object-oriented programming to save countless troubles.

Now, keeping the metaclass unchanged, we can continue to create the Sayolala, Nihao class, as follows:

 # Two came from one: Create class class Sayolala ( object ,metaclass = SayMetaClass ) : pass # three came from two: create a real column s = Sayolala () # all things came from two: call the instance method s.say_Sayolala ( 'japan!' ) 

Ausgabe

 Sayolala, japan! 

Can also speak Chinese

 # Two came from one: Create class class Nihao(object ,metaclass = SayMetaClass ) : pass # two students three: create a real column n = Nihao() # Three things: call the instance method n.say_Nihao ( '中 中华!' ) 

Ausgabe

 Nihao, China! 

Another small example:

 # one came from truth. class ListMetaclass (type) : def __new__ ( cls ,name, bases , attrs) : # Talent: Bind values ​​by the add method attrs[ 'add' ] = lambda self, value: self.append(value) return type . __new__ ( cls ,name ,bases ,attrs ) # One lifetime class MyList ( list , Metaclass = ListMetaclass ) : pass # Two students three L = MyList () # Three things L.add( 1 ) 

Now we print L

 print(L) >>> [ 1 ] 

The ordinary list does not have an add() method

 L2 = list () L2 . add ( 1 ) >>> AttributeError : 'list' Object Has no attribute 'add' 

awesome! Learned here, have you experienced the joy of the Creator?

 Everything in the python world is at your fingertips. 

Young Creator, please follow me to create a new world.

We choose two areas, one is the core idea of ​​Django, “Object Relational Mapping”, object-relational mapping, referred to as ORM.

This is a major Django difficulty, but after learning the metaclass, everything becomes clear. Your understanding of Django will be even better!

Another area is reptiles (hackers), an automatic search of available agents on the network, and then changing IP to break other people’s anti-crawler restrictions.

These two skills are very useful and very fun!

Challenge 1: Create ORM by Metaclass

Prepare to create a Field class

 class Field ( object ) : def __init__ ( self, name, column_type ) : Self.name = name Self.column_type = column_type def __str__ ( self ) : return '< %s:%s>' % ( self . __class__ . __name__ , self. name ) 

Its role is

When the Field class is instantiated, it will get two parameters, name and column_type. They will be bound to Field’s private property. If you want to convert the Field into a string, it will return “Field:XXX”. XXX is passed in. Name name.

Preparation: Create StringField and IntergerField

 class StringField ( Field ) : def __init__ ( self , name ) : super( StringField , self). __init__ ( name , 'varchar(100)' ) class IntegerField ( Field ) : def __init__ ( self ,name) : super( IntegerField , self). __init__ ( name , 'bigint' ) 

Its role is

When the StringField, IntegerField instance is initialized, the parent’s initialization method is automatically called.

one came from the truth

 class ModelMetaclass ( type ) : def __new__ ( cls ,name, bases , attrs) : Ifname== 'Model' : Return Type . __new__ ( cls ,name, bases , attrs) print( 'Found model: %s' % name ) Mappings = dict () for k , v In attrs. items () : If Isinstance ( v , Field ) : print( 'Found mapping: %s ==> %s' % ( k , v )) Mappings [ k ] = v for k In Mappings . keys () : attrs. pop ( k ) attrs[ '__mappings__' ] = mappings # Save the mapping between attributes and columns attrs[ '__table__' ] = name # Assume that the table name and class name are the same Return Type . __new__ ( cls ,name, bases , attrs) 

It does the following things

 Create a new dictionary mapping Each property of the class is traversed through its .items() key-value pair. If the value is a Field class, the key is printed and the key is bound to the mapping dictionary. Delete the property that was just passed in as the Field class. Create a special __mappings__ attribute and save the dictionary mapping. Create a special __table__ attribute and save the name of the passed in class. 

two came from one

 class Model ( dict , Metaclass = ModelMetaclass ) : def __init__ ( self , ** kwarg ) : super(model , self). __init__ ( ** kwarg ) def __getattr__ ( self , Key ) : Try : Return self[ key ] except KeyError : Raise AttributeError ( "'Model' object has no attribute '%s'" % key ) def __setattr__ ( self , Key , Value ) : self[ key ] = value # Simulate table creation operation def save( self ) : Fields = [] Args = [] for k , v In self. __mappings__ . items () : Fields . append ( v . name ) Args . append ( getattr ( self , k , None )) Sql = 'insert into %s (%s) values ​​(%s)' % ( self . __table__ , ',' . join ( fields ), ',' . join ([ str ( i ) for i In Args ])) print( 'SQL: %s' % sql ) print( 'ARGS: %s' % str ( args )) 

If you create a subclass User from themodel:

 class User (model ) : # Define the mapping of attributes's attributes to columns: Id = IntegerField ( 'id' ) name= StringField ( 'username' ) Email = StringField ( 'email' ) Password = StringField ( 'password' ) 

At this time

Id= IntegerField(‘id’) will automatically resolve to:

Model. setattr (self, ‘id’, IntegerField(‘id’))

Because IntergerField(‘id’) is an instance of Field’s subclass, the metaclass’s new is automatically triggered, so the IntergerField(‘id’) is stored in mappings and the key-value pair is deleted.

Two students, three students, all things

When you initialize an instance and call the save() method

 u = User ( id = 12345 ,name= 'Batman' , Email = 'batman@nasa.org' , Password = 'iamback' ) u . save () 

At this time, the process of two students is completed first:

 First call Model.__setattr__ to load key values ​​into private objects Then call the "genius" of the metaclass, ModelMetaclass.__new__, and private objects in the Model, as long as they are instances of Field, are automatically stored in u.__mappings__. 

The next step is to complete the three things:

Simulate data inventory operations through u.save(). Here we just do a little traversal mappings operation, virtual sql and print, in reality, by entering the sql statement and the database to run.

The output is

 Found model : User Found mapping : name ==> < StringField : username > Found mapping : password ==> < StringField : password > Found mapping : id ==> < IntegerField : id > Found mapping : email ==> < StringField : email > SQL : insert into User ( username , password , id , email ) Values ( Batman , iamback , 12345 , batman @ nasa . org ) ARGS : [ 'Batman' , 'iamback' 12345 , 'batman@nasa.org' ] Young Creator, you have experienced with me the great course of evolution of Everything from the Tao, which is also the core principle of the Model section in Django. Next, join me in a more fun reptile battle (well, you are now a junior hacker): crawling web agents! 

Challenge II: Crawling of Network Agents

Prepare to climb a page to play

Please make sure that both packages, requests and pyquery, are installed.

 # File: get_page.py import Requests Base_headers = { 'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36' , 'Accept-Encoding' : 'gzip, deflate, sdch' , 'Accept-Language' : 'zh-CN,zh;q=0.8' } def Get_page ( url ) : Headers = dict ( base_headers ) print( 'Getting' , Url ) Try : r = requests . get ( url , Headers = headers ) print( 'Getting result' , Url , r . status_code ) If r . status_code == 200 : Return r . exceptConnectionError : print( 'Crawling Failed' , Url ) Return None 

Here, we use the request package to climb out of Baidu’s source code.

Try to try Baidu

Stick this paragraph behind get_page.py and try deleting

 If ( __name__ == '__main__' ) : Rs = get_page ( 'https://www.baidu.com' ) print( 'result: ' , Rs ) 

Try to catch agents

Stick this paragraph behind get_page.py and try deleting

 If ( __name__ == '__main__' ) : from Pyquery import PyQuery as Pq Start_url = 'http://www.proxy360.cn/Region/China' print( 'Crawling' , Start_url ) Html = get_page ( start_url ) If Html : Doc = pq ( html ) Lines = doc ( 'div[name="list_proxy_ip"]' ). items () for Line in Lines : Ip = line . find ( '.tbBottomLine:nth-child(1)' ). text () Port = line . find ( '.tbBottomLine:nth-child(2)' ). text () print( ip + ':' + port ) Next, go to the topic: Use the metaclass batch fetch proxy 

Batch processing crawling agent

 from Getpage import Get_page from Pyquery import PyQuery as Pq # one came from truth: Create metaclass of extraction agent class ProxyMetaclass ( type ) : """ Metaclass, added in the FreeProxyGetter class __CrawlFunc__ and __CrawlFuncCount__ Two parameters, which represent the crawler function and the number of crawler functions, respectively. """ def __new__ ( cls ,name, bases , attrs) : Count = 0 attrs[ '__CrawlFunc__' ] = [] attrs[ '__CrawlName__' ] = [] for k , v In attrs. items () : If 'crawl_' In k : attrs[ '__CrawlName__' ]. append ( k ) attrs[ '__CrawlFunc__' ]. append ( v ) Count += 1 for k In attrs[ '__CrawlName__' ] : attrs. pop ( k ) attrs[ '__CrawlFuncCount__' ] = count Return Type . __new__ ( cls ,name, bases , attrs) # two came from one: Create an agent to get the class class ProxyGetter ( object , Metaclass = ProxyMetaclass ) : def Get_raw_proxies ( self , Site ) : Proxies = [] print( 'Site' , Site ) for Func in self. __CrawlFunc__ : If Func . __name__ == site : This_page_proxies = func ( self ) for Proxy in This_page_proxies : print( 'Getting' , Proxy , 'from' , Site ) Proxies . append ( proxy ) Return Proxies def Crawl_daili66 ( self , Page_count = 4 ) : Start_url = 'http://www.66ip.cn/{}.html' Urls = [ start_url . format ( page ) for Page in Range ( 1 , Page_count + 1 )] for Url in Urls : print( 'Crawling' , Url ) Html = get_page ( url ) If Html : Doc = pq ( html ) Trs = doc ( '.containerbox table tr:gt(0)' ). items () for Tr in Trs : Ip = tr . find ( 'td:nth-child(1)' ). text () Port = tr . find ( 'td:nth-child(2)' ). text () Yield ':' . join ([ ip , Port ]) def Crawl_proxy360 ( self ) : Start_url = 'http://www.proxy360.cn/Region/China' print( 'Crawling' , Start_url ) Html = get_page ( start_url ) If Html : Doc = pq ( html ) Lines = doc ( 'div[name="list_proxy_ip"]' ). items () for Line in Lines : Ip = line . find ( '.tbBottomLine:nth-child(1)' ). text () Port = line . find ( '.tbBottomLine:nth-child(2)' ). text () Yield ':' . join ([ ip , Port ]) def Crawl_goubanjia ( self ) : Start_url = 'http://www.goubanjia.com/free/gngn/index.shtml' Html = get_page ( start_url ) If Html : Doc = pq ( html ) Tds = doc ( 'td.ip' ). items () for Td in Tds : Td . find ( 'p' ). remove () Yield Td . text (). replace ( ' ' , '' ) If __name__ == '__main__' : # Two students three: Instantiate ProxyGetter Crawler = ProxyGetter () print(crawler . __CrawlName__ ) # Three things for Site_label in Range ( crawler . __CrawlFuncCount__ ) : Site = crawler . __CrawlName__ [ site_label ] myProxies = crawler . get_raw_proxies ( site ) 

one came from truth: In the metaclass new , he did four things:

 Push the name of the class method that starts with "crawl_" into ProxyGetter.__CrawlName__ Push the class method that starts with "crawl_" itself into ProxyGetter.__CrawlFunc__ Calculate the number of class methods that match "crawl_" Delete all class methods that match "crawl_" how about it? Is it very similar to the __mappings__ process used to create an ORM? 

two came from one: The class defines the method of using pyquery to grab page elements

Each of the agents shown on the page was crawled from three free agent sites.

If you are not familiar with yield usage, check out: Liao Xuefeng’s python tutorial: generator

three came from two: create instance object crawler

slightly

Three things: Traversing every CrawlFunc

 Above ProxyGetter.__CrawlName__, get the URL name that can be crawled. Trigger class method ProxyGetter.get_raw_proxies(site) Traverse ProxyGetter.__CrawlFunc__, if the method name and URL are the same, then execute this method Integrate the proxy obtained from each URL into an array output. So. . . How to use bulk agents, impact other people's websites, capture other people's passwords, frantically advertise water stickers, and regularly harass customers? Uh! Think it! These self-realization! If you do not realize it, please listen to the next decomposition! 

The young Creator, the tool for creating the world, is already in your hands. Please use its power to the fullest!

Remember the wielding tool’s mouth:

 One came from truth, two came from one, three came from two, all the thing came from three. Who am I, where do I come from, where do I go