Antworten:
Eine Metaklasse ist die Klasse einer Klasse. Eine Klasse definiert, wie sich eine Instanz der Klasse (dh ein Objekt) verhält, während eine Metaklasse definiert, wie sich eine Klasse verhält. Eine Klasse ist eine Instanz einer Metaklasse.
Während Sie in Python beliebige Callables für Metaklassen verwenden können (wie Jerub zeigt), besteht der bessere Ansatz darin, es selbst zu einer tatsächlichen Klasse zu machen. type
ist die übliche Metaklasse in Python. type
ist selbst eine Klasse und es ist ein eigener Typ. Sie werden nicht in der Lage sein, so etwas wie type
rein in Python neu zu erstellen , aber Python betrügt ein wenig. Um Ihre eigene Metaklasse in Python zu erstellen, möchten Sie wirklich nur eine Unterklasse erstellen type
.
Eine Metaklasse wird am häufigsten als Klassenfabrik verwendet. Wenn Sie ein Objekt durch Aufrufen der Klasse erstellen, erstellt Python eine neue Klasse (wenn die Anweisung 'class' ausgeführt wird), indem Sie die Metaklasse aufrufen. In Kombination mit den Normalen __init__
und __new__
Methoden können Sie mit Metaklassen beim Erstellen einer Klasse zusätzliche Dinge tun, z. B. die neue Klasse bei einer Registrierung registrieren oder die Klasse durch etwas ganz anderes ersetzen.
Wenn die class
Anweisung ausgeführt wird, führt Python zuerst den Hauptteil der class
Anweisung als normalen Codeblock aus. Der resultierende Namespace (ein Diktat) enthält die Attribute der zukünftigen Klasse. Die Metaklasse wird bestimmt, indem die Basisklassen der zukünftigen Klasse (Metaklassen werden vererbt), das __metaclass__
Attribut der zukünftigen Klasse (falls vorhanden) oder die __metaclass__
globale Variable betrachtet werden. Die Metaklasse wird dann mit dem Namen, den Basen und den Attributen der Klasse aufgerufen, um sie zu instanziieren.
Metaklassen definieren jedoch tatsächlich den Typ einer Klasse, nicht nur eine Fabrik dafür, sodass Sie viel mehr damit tun können. Sie können beispielsweise normale Methoden für die Metaklasse definieren. Diese Metaklassenmethoden sind insofern wie Klassenmethoden, als sie für die Klasse ohne Instanz aufgerufen werden können, aber sie sind auch nicht wie Klassenmethoden, da sie nicht für eine Instanz der Klasse aufgerufen werden können. type.__subclasses__()
ist ein Beispiel für eine Methode für die type
Metaklasse. Sie können auch die normale ‚Magie‘ Methoden definieren, wie __add__
, __iter__
und __getattr__
zu implementieren oder zu ändern , wie die Klasse verhält.
Hier ist ein aggregiertes Beispiel für die einzelnen 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__
__metaclass__
dies in Python 3 nicht unterstützt wird. In Python 3 class MyObject(metaclass=MyType)
finden Sie unter python.org/dev/peps/pep-3115 und in der folgenden Antwort.
Bevor Sie Metaklassen verstehen, müssen Sie Klassen in Python beherrschen. Und Python hat eine sehr eigenartige Vorstellung davon, was Klassen sind, die aus der Smalltalk-Sprache entlehnt sind.
In den meisten Sprachen sind Klassen nur Codeteile, die beschreiben, wie ein Objekt erstellt wird. Das stimmt auch in Python:
>>> class ObjectCreator(object):
... pass
...
>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>
Aber Klassen sind mehr als das in Python. Klassen sind auch Objekte.
Ja, Objekte.
Sobald Sie das Schlüsselwort verwenden class
, führt Python es aus und erstellt ein OBJEKT. Die Anleitung
>>> class ObjectCreator(object):
... pass
...
Erstellt im Speicher ein Objekt mit dem Namen "ObjectCreator".
Dieses Objekt (die Klasse) kann selbst Objekte (die Instanzen) erstellen, und deshalb ist es eine Klasse .
Trotzdem ist es ein Objekt und daher:
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>
Da Klassen Objekte sind, können Sie sie wie jedes Objekt im laufenden Betrieb erstellen.
Zunächst können Sie eine Klasse in einer Funktion erstellen, indem Sie class
:
>>> def choose_class(name):
... if name == 'foo':
... class Foo(object):
... pass
... return Foo # return the class, not an instance
... else:
... class Bar(object):
... pass
... return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>
Aber es ist nicht so dynamisch, da Sie immer noch die ganze Klasse selbst schreiben müssen.
Da Klassen Objekte sind, müssen sie von etwas generiert werden.
Wenn Sie das class
Schlüsselwort verwenden, erstellt Python dieses Objekt automatisch. Aber wie bei den meisten Dingen in Python gibt es eine Möglichkeit, dies manuell zu tun.
Erinnerst type
du dich an die Funktion ? Die gute alte Funktion, mit der Sie wissen, welcher Typ ein Objekt ist:
>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>
Nun, type
hat eine ganz andere Fähigkeit, es kann auch Klassen im laufenden Betrieb erstellen. type
kann die Beschreibung einer Klasse als Parameter verwenden und eine Klasse zurückgeben.
(Ich weiß, es ist albern, dass dieselbe Funktion je nach den von Ihnen übergebenen Parametern zwei völlig unterschiedliche Verwendungszwecke haben kann. Dies ist ein Problem aufgrund der Abwärtskompatibilität in Python.)
type
funktioniert so:
type(name, bases, attrs)
Wo:
name
: Name der Klassebases
: Tupel der übergeordneten Klasse (für die Vererbung kann leer sein)attrs
: Wörterbuch mit Attributnamen und -wertenz.B:
>>> class MyShinyClass(object):
... pass
kann manuell auf folgende Weise erstellt werden:
>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>
Sie werden feststellen, dass wir "MyShinyClass" als Namen der Klasse und als Variable für die Klassenreferenz 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. Damit:
>>> class Foo(object):
... bar = True
Kann übersetzt werden in:
>>> Foo = type('Foo', (), {'bar':True})
Und als normale Klasse verwendet:
>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True
Und natürlich können Sie davon erben, also:
>>> class FooChild(Foo):
... pass
wäre:
>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True
Schließlich möchten Sie Ihrer Klasse Methoden hinzufügen. Definieren Sie einfach eine Funktion mit der richtigen Signatur und weisen Sie 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 nach dem dynamischen Erstellen der Klasse noch mehr Methoden hinzufügen, genau wie Sie einem normal erstellten Klassenobjekt Methoden hinzufügen.
>>> def echo_bar_more(self):
... print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True
Sie sehen, wohin wir gehen: In Python sind Klassen Objekte, und Sie können eine Klasse im laufenden Betrieb dynamisch erstellen.
Dies ist, was Python tut, wenn Sie das Schlüsselwort verwenden class
, und dies unter Verwendung einer Metaklasse.
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 schaffen diese Objekte. Sie sind die Klassen der Klassen, Sie können sie sich so vorstellen:
MyClass = MetaClass()
my_object = MyClass()
Sie haben gesehen, dass type
Sie so etwas tun können:
MyClass = type('MyClass', (), {})
Das liegt daran, dass die Funktion type
tatsächlich eine Metaklasse ist. type
ist die Metaklasse, mit der Python alle Klassen hinter den Kulissen erstellt.
Jetzt fragen Sie sich, warum zum Teufel es in Kleinbuchstaben geschrieben ist und nicht Type
?
Nun, ich denke, es ist eine Frage der Konsistenz mit str
der Klasse, die Zeichenfolgenobjekte erstellt, und int
der Klasse, die Ganzzahlobjekte erstellt. type
ist nur die Klasse, die Klassenobjekte erstellt.
Sie sehen das, indem Sie das __class__
Attribut überprüfen .
Alles, und ich meine alles, ist ein Objekt in Python. Dazu gehören Ints, Strings, Funktionen und Klassen. Alle von ihnen sind Objekte. Und 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 das __class__
von irgendjemandem __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 integrierte Metaklasse, die Python verwendet, aber Sie können natürlich Ihre eigene Metaklasse erstellen.
__metaclass__
AttributIn Python 2 können Sie __metaclass__
beim Schreiben einer Klasse ein Attribut hinzufügen (Informationen zur Python 3-Syntax finden Sie im nächsten Abschnitt):
class Foo(object):
__metaclass__ = something...
[...]
In diesem Fall verwendet Python die Metaklasse, um die Klasse zu erstellen Foo
.
Vorsicht, es ist schwierig.
Sie schreiben class Foo(object)
zuerst, aber das Klassenobjekt Foo
wird noch nicht im Speicher erstellt.
Python sucht __metaclass__
in der Klassendefinition. Wenn es es findet, wird es verwendet, um die Objektklasse zu erstellen Foo
. Wenn dies nicht der Fall ist, wird
type
die Klasse erstellt.
Lies das mehrmals.
Wenn Sie das tun:
class Foo(Bar):
pass
Python macht Folgendes:
Gibt es ein __metaclass__
Attribut in Foo
?
Wenn ja, erstellen Sie im Speicher ein Klassenobjekt (ich sagte ein Klassenobjekt, bleiben Sie hier bei mir), mit dem Namen, Foo
indem Sie das verwenden, was sich darin befindet __metaclass__
.
Wenn Python nicht finden kann, sucht __metaclass__
es __metaclass__
auf MODULE-Ebene nach einem und versucht, dasselbe zu tun (jedoch nur für Klassen, die nichts erben, im Grunde alte Klassen).
Wenn es dann überhaupt keine findet __metaclass__
, verwendet es die Bar
eigene Metaklasse (die erste übergeordnete) (möglicherweise die Standardeinstellung type
), um das Klassenobjekt zu erstellen.
Achten Sie hier darauf, dass das __metaclass__
Attribut nicht vererbt wird, sondern die Metaklasse von parent ( Bar.__class__
). Wenn Bar
ein __metaclass__
Attribut verwendet wird, das Bar
mit type()
(und nicht type.__new__()
) erstellt wurde, erben die Unterklassen dieses Verhalten nicht.
Die große Frage ist nun, was können Sie eingeben __metaclass__
?
Die Antwort lautet: etwas, das eine Klasse erstellen kann.
Und was kann eine Klasse schaffen? type
oder irgendetwas, das es unterordnet oder verwendet.
Die Syntax zum Festlegen der Metaklasse wurde in Python 3 geändert:
class Foo(object, metaclass=something):
...
Das heißt, das __metaclass__
Attribut wird nicht mehr zugunsten eines Schlüsselwortarguments in der Liste der Basisklassen verwendet.
Das Verhalten von Metaklassen bleibt jedoch weitgehend gleich .
Eine Sache, die Metaklassen in Python 3 hinzugefügt wurde, ist, dass Sie Attribute auch als Schlüsselwortargumente an eine Metaklasse übergeben können, wie folgt:
class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
...
Lesen Sie den folgenden Abschnitt, um zu erfahren, wie Python damit umgeht.
Der Hauptzweck einer Metaklasse besteht darin, die Klasse beim Erstellen automatisch zu ändern.
Dies tun Sie normalerweise für APIs, bei denen Sie Klassen erstellen möchten, die dem aktuellen Kontext entsprechen.
Stellen Sie sich ein dummes Beispiel vor, in dem Sie entscheiden, dass alle Klassen in Ihrem Modul ihre Attribute in Großbuchstaben schreiben sollen. Es gibt verschiedene Möglichkeiten, dies zu tun, aber eine Möglichkeit besteht darin, __metaclass__
auf Modulebene festzulegen.
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.
Glücklicherweise __metaclass__
kann es tatsächlich jede aufrufbare Klasse sein, es muss keine formelle Klasse sein (ich weiß, etwas mit 'Klasse' im Namen muss keine Klasse sein, mach eine Figur ... aber es ist hilfreich).
Wir beginnen also 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_attrs):
"""
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_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in future_class_attrs.items()
}
# let `type` do the class creation
return type(future_class_name, future_class_parents, uppercase_attrs)
__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'
Lass uns nachsehen:
>>> hasattr(Foo, 'bar')
False
>>> hasattr(Foo, 'BAR')
True
>>> Foo.BAR
'bip'
Lassen Sie uns jetzt genau das Gleiche tun, aber eine echte Klasse für eine Metaklasse verwenden:
# 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_attrs):
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in future_class_attrs.items()
}
return type(future_class_name, future_class_parents, uppercase_attrs)
Lassen Sie uns das Obige umschreiben, aber mit kürzeren und realistischeren Variablennamen, jetzt wo wir wissen, was sie bedeuten:
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, attrs):
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in attrs.items()
}
return type(clsname, bases, uppercase_attrs)
Möglicherweise haben Sie das zusätzliche Argument bemerkt cls
. Es gibt nichts Besonderes: __new__
Empfängt immer die Klasse, in der es definiert ist, als ersten Parameter. Genau wie self
bei normalen Methoden, die die Instanz als ersten Parameter erhalten, oder der definierenden Klasse für Klassenmethoden.
Aber das ist nicht richtig OOP. Wir rufen type
direkt an und überschreiben oder rufen nicht die der Eltern an __new__
. Lassen Sie uns das stattdessen tun:
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, attrs):
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in attrs.items()
}
return type.__new__(cls, clsname, bases, uppercase_attrs)
Wir können es noch sauberer machen, indem wir verwenden super
, was die Vererbung erleichtert (denn ja, Sie können Metaklassen haben, die von Metaklassen erben, vom Typ erben):
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, attrs):
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in attrs.items()
}
return super(UpperAttrMetaclass, cls).__new__(
cls, clsname, bases, uppercase_attrs)
Oh, und in Python 3, wenn Sie diesen Aufruf mit Schlüsselwortargumenten wie folgt ausführen:
class Foo(object, metaclass=MyMetaclass, kwarg1=value1):
...
Dies wird in der Metaklasse übersetzt, um es zu verwenden:
class MyMetaclass(type):
def __new__(cls, clsname, bases, dct, kwargs1=default):
...
Das ist es. Metaklassen haben wirklich nichts mehr zu bieten.
Der Grund für die Komplexität des Codes bei der Verwendung von Metaklassen liegt nicht in Metaklassen, sondern darin, dass Sie normalerweise Metaklassen verwenden, um verdrehte Dinge zu erledigen, die auf Introspektion, Manipulation der Vererbung, Variablen wie __dict__
usw. beruhen .
In der Tat sind Metaklassen besonders nützlich, um schwarze Magie und damit komplizierte Dinge zu tun. Aber für sich sind sie einfach:
Da __metaclass__
jede aufrufbare annehmen können, warum sollten Sie eine Klasse verwenden , da ist es offensichtlich kompliziert mehr?
Dafür gibt es mehrere Gründe:
UpperAttrMetaclass(type)
, wissen Sie, was folgen wird__new__
, __init__
und __call__
. So können Sie verschiedene Dinge tun. Selbst wenn Sie normalerweise alles erledigen können __new__
, sind einige Leute einfach komfortabler __init__
.Nun die große Frage. Warum sollten Sie eine obskure fehleranfällige Funktion verwenden?
Normalerweise tun Sie das nicht:
Metaklassen sind tiefere Magie, über die sich 99% der Benutzer niemals Sorgen machen sollten. Wenn Sie sich fragen, ob Sie sie brauchen, tun Sie das nicht (die Menschen, die sie tatsächlich 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. Hier können Sie Folgendes definieren:
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
Aber wenn Sie dies tun:
person = Person(name='bob', age='35')
print(person.age)
Es wird kein IntegerField
Objekt zurückgegeben. Es gibt ein zurück int
und kann es sogar direkt aus der Datenbank übernehmen.
Dies ist möglich, weil models.Model
definiert __metaclass__
und es etwas Magie verwendet, die Person
das soeben definierte mit einfachen Anweisungen in einen komplexen Hook für ein Datenbankfeld verwandelt .
Django lässt etwas Komplexes einfach aussehen, indem es eine einfache API verfügbar macht und Metaklassen verwendet. Dabei wird Code aus dieser API neu erstellt, um die eigentliche Aufgabe hinter den Kulissen zu erledigen.
Erstens wissen Sie, dass Klassen Objekte sind, die Instanzen erstellen können.
In der Tat sind Klassen selbst Instanzen. Von Metaklassen.
>>> class Foo(object): pass
>>> id(Foo)
142630324
Alles ist ein Objekt in Python, und alle sind entweder Instanzen von Klassen oder Instanzen von Metaklassen.
Außer type
.
type
ist eigentlich eine eigene Metaklasse. Dies ist nichts, was Sie in reinem Python reproduzieren könnten, und dies geschieht durch ein wenig Betrug auf der Implementierungsebene.
Zweitens sind Metaklassen kompliziert. Möglicherweise möchten Sie sie nicht für sehr einfache Klassenänderungen verwenden. Sie können Klassen mit zwei verschiedenen Techniken ändern:
In 99% der Fälle, in denen Sie eine Klassenänderung benötigen, ist es besser, diese zu verwenden.
In 98% der Fälle müssen Sie jedoch überhaupt keine Klassenänderungen vornehmen.
models.Model
nicht verwendet __metaclass__
, sondern class Model(metaclass=ModelBase):
auf eine ModelBase
Klasse verweist , die dann die oben erwähnte Metaklassenmagie ausführt. Guter Eintrag! Hier ist die Django-Quelle: github.com/django/django/blob/master/django/db/models/…
__metaclass__
Attribut nicht vererbt wird, sondern die Metaklasse von parent ( Bar.__class__
). Wenn Bar
ein __metaclass__
Attribut verwendet wird, das Bar
mit type()
(und nicht type.__new__()
) erstellt wurde, erben die Unterklassen dieses Verhalten nicht. >> - Könnten Sie / jemand diese Passage etwas näher erläutern?
Now you wonder why the heck is it written in lowercase, and not Type?
- Nun, weil es in C implementiert ist - es ist der gleiche Grund, warum defaultdict in Kleinbuchstaben geschrieben ist, während OrderedDict (in Python 2) normal ist. CamelCase
Beachten Sie, dass diese Antwort für Python 2.x gilt, wie es 2008 geschrieben wurde. Metaklassen unterscheiden sich in 3.x geringfügig.
Metaklassen sind die geheime Sauce, mit der „Klasse“ funktioniert. Die Standardmetaklasse für ein neues Stilobjekt heißt "Typ".
class type(object)
| type(object) -> the object's type
| type(name, bases, dict) -> a new type
Metaklassen benötigen 3 Argumente. ' Name ', ' Basen ' und ' Diktat '
Hier beginnt das Geheimnis. Suchen Sie in dieser Beispielklassendefinition, woher Name, Basen und das Diktat stammen.
class ThisIsTheName(Bases, Are, Here):
All_the_code_here
def doesIs(create, a):
dict
Definieren wir 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, Initialised
das durch die Metaklasse init_attributes
entsteht, nicht an eine Unterklasse von übergeben wird Initialised
.
Hier ist ein noch konkreteres Beispiel, das zeigt, wie Sie 'Typ' in eine Unterklasse umwandeln können, um eine Metaklasse zu erstellen, die beim Erstellen der Klasse eine Aktion ausführt. 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
Andere haben erklärt, wie Metaklassen funktionieren und wie sie in das Python-Typsystem passen. Hier ist ein Beispiel dafür, wofür sie verwendet werden können. In einem von mir geschriebenen Testframework wollte ich die Reihenfolge verfolgen, in der Klassen definiert wurden, damit ich sie später in dieser Reihenfolge instanziieren kann. 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 dann ein Klassenattribut, _order
das die Reihenfolge aufzeichnet, in der die Klassen definiert wurden.
__init__(self)
Aussage type(self)._order = MyBase.counter; MyBase.counter += 1
?
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 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 Personenobjekte jedoch mit allen möglichen nützlichen Methoden gefüllt. In der Quelle finden Sie einige erstaunliche Metaclassery.
Ich denke, die ONLamp-Einführung in die Metaklassenprogrammierung ist gut geschrieben und bietet eine wirklich gute Einführung in das Thema, 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 eine Blaupause für die Erstellung einer Instanz, eine Metaklasse ist eine Blaupause für die Erstellung einer Klasse. Es ist leicht zu erkennen, dass in Python-Klassen auch erstklassige Objekte sein müssen, um dieses Verhalten zu ermöglichen.
Ich habe selbst noch nie eine geschrieben, aber ich denke, eine der schönsten Anwendungen 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 Klasse erstellt, haben alle Mitglieder die Möglichkeit, die Klasse selbst anzupassen.
Die Sache, die noch zu sagen bleibt, ist: Wenn Sie nicht wissen, was Metaklassen sind, beträgt die Wahrscheinlichkeit, dass Sie sie nicht benötigen, 99%.
Was sind Metaklassen? Wofür benutzt du sie?
TLDR: Eine Metaklasse instanziiert und definiert das Verhalten einer Klasse genau wie eine Klasse das Verhalten einer Instanz.
Pseudocode:
>>> Class(...)
instance
Das Obige sollte vertraut aussehen. Woher kommt Class
das? Es ist eine Instanz einer Metaklasse (auch Pseudocode):
>>> Metaclass(...)
Class
In echtem Code können wir die Standard-Metaklasse übergeben, type
alles, 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'>
Eine Klasse gehört zu einer Instanz wie eine Metaklasse zu einer 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 definieren, type
instanziieren wir sie ebenfalls:
>>> 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 Python sie ausführt, wird das Klassenobjekt mithilfe einer Metaklasse instanziiert (die wiederum zum Instanziieren von Instanzen dieser Klasse verwendet wird).
So wie wir Klassendefinitionen verwenden können, um das Verhalten benutzerdefinierter Objektinstanzen zu ändern, können wir eine Metaklassen-Klassendefinition verwenden, um das Verhalten eines Klassenobjekts zu ändern.
Wofür können sie verwendet werden? Aus den Dokumenten :
Die Verwendungsmöglichkeiten für Metaklassen sind grenzenlos. Einige der untersuchten Ideen umfassen Protokollierung, Schnittstellenprüfung, automatische Delegierung, automatische Erstellung von Eigenschaften, Proxys, Frameworks und automatische Ressourcensperrung / -synchronisierung.
Dennoch wird Benutzern normalerweise empfohlen, die Verwendung von Metaklassen zu vermeiden, sofern dies nicht unbedingt erforderlich ist.
Wenn Sie beispielsweise eine Klassendefinition schreiben,
class Foo(object):
'demo'
Sie instanziieren ein Klassenobjekt.
>>> Foo
<class '__main__.Foo'>
>>> isinstance(Foo, type), isinstance(Foo, object)
(True, True)
type
Dies entspricht dem funktionalen Aufrufen mit den entsprechenden Argumenten und dem Zuweisen des Ergebnisses zu einer Variablen mit diesem Namen:
name = 'Foo'
bases = (object,)
namespace = {'__doc__': 'demo'}
Foo = type(name, bases, namespace)
Beachten Sie, dass einige Dinge automatisch __dict__
zum Namespace hinzugefügt werden :
>>> 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
.
(Seite A-Note auf dem Inhalt der Klasse __dict__
: __module__
ist da , weil Klassen müssen wissen , wo sie definiert sind, und , __dict__
und __weakref__
es gibt , weil wir nicht definieren __slots__
- wenn wir definieren__slots__
wir in den Fällen , ein wenig Platz sparen werden, wie wir können sie verbieten __dict__
und __weakref__
ausschließen. Zum Beispiel:
>>> Baz = type('Bar', (object,), {'__doc__': 'demo', '__slots__': ()})
>>> Baz.__dict__
mappingproxy({'__doc__': 'demo', '__slots__': (), '__module__': '__main__'})
... Aber ich schweife ab.)
type
wie jede andere Klassendefinition erweitern:Hier ist die Standardeinstellung __repr__
für Klassen:
>>> Foo
<class '__main__.Foo'>
Eines der wertvollsten Dinge, die wir beim Schreiben eines Python-Objekts standardmäßig tun können, ist, es mit einem Gut zu versehen __repr__
. Wenn wir anrufen help(repr)
, erfahren wir, dass es einen guten Test für einen gibt __repr__
, der auch einen Test für Gleichheit erfordert - obj == eval(repr(obj))
. Die folgende einfache Implementierung von __repr__
und __eq__
für Klasseninstanzen unserer Typklasse bietet uns eine Demonstration, die den Standard __repr__
von Klassen verbessern kann :
class Type(type):
def __repr__(cls):
"""
>>> Baz
Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
>>> eval(repr(Baz))
Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
"""
metaname = type(cls).__name__
name = cls.__name__
parents = ', '.join(b.__name__ for b in cls.__bases__)
if parents:
parents += ','
namespace = ', '.join(': '.join(
(repr(k), repr(v) if not isinstance(v, type) else v.__name__))
for k, v in cls.__dict__.items())
return '{0}(\'{1}\', ({2}), {{{3}}})'.format(metaname, name, parents, namespace)
def __eq__(cls, other):
"""
>>> Baz == eval(repr(Baz))
True
"""
return (cls.__name__, cls.__bases__, cls.__dict__) == (
other.__name__, other.__bases__, other.__dict__)
Wenn wir nun ein Objekt mit dieser Metaklasse erstellen, bietet das __repr__
Echo in der Befehlszeile einen viel weniger hässlichen Anblick als die Standardeinstellung:
>>> class Bar(object): pass
>>> Baz = Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
>>> Baz
Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
Mit einer __repr__
für die Klasseninstanz definierten Funktion können wir unseren Code besser debuggen. Eine weitere Überprüfung mit eval(repr(Class))
ist jedoch unwahrscheinlich (da Funktionen von ihren Standardeinstellungen eher nicht bewertet werden können __repr__
).
__prepare__
ein NamespaceWenn 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-Diktat 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 Dokumentation übernommen wurde - die neue Aufzählung in der Standardbibliothek übernimmt dies.
Wir haben also eine Metaklasse instanziiert, indem wir eine Klasse erstellt haben. Wir können die Metaklasse auch wie jede andere Klasse behandeln. Es hat eine Reihenfolge der Methodenauflösung:
>>> inspect.getmro(OrderedType)
(<class '__main__.OrderedType'>, <class '__main__.Type'>, <class 'type'>, <class 'object'>)
Und es hat ungefähr das Richtige repr
(was wir nicht mehr bewerten 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>})
Python 3-Update
Es gibt (zu diesem Zeitpunkt) zwei Schlüsselmethoden in einer Metaklasse:
__prepare__
, und__new__
__prepare__
Mit dieser Option können Sie eine benutzerdefinierte Zuordnung (z. B. eine OrderedDict
) angeben, die als Namespace verwendet wird, während die Klasse erstellt wird. Sie müssen eine Instanz eines beliebigen Namespace zurückgeben. Wenn Sie keine __prepare__
normale implementieren, dict
wird verwendet.
__new__
ist verantwortlich für die eigentliche Erstellung / Änderung der endgültigen Klasse.
Eine nackte Metaklasse, die nichts mehr tut, möchte:
class Meta(type):
def __prepare__(metaclass, cls, bases):
return dict()
def __new__(metacls, cls, bases, clsdict):
return super().__new__(metacls, cls, bases, clsdict)
Ein einfaches Beispiel:
Angenommen, Sie möchten, dass ein einfacher Validierungscode für Ihre Attribute ausgeführt wird - so wie es immer ein int
oder ein sein muss str
. Ohne eine Metaklasse würde Ihre Klasse ungefähr so aussehen:
class Person:
weight = ValidateType('weight', int)
age = ValidateType('age', int)
name = ValidateType('name', str)
Wie Sie sehen, müssen Sie den Namen des Attributs zweimal wiederholen. Dies ermöglicht Tippfehler zusammen mit irritierenden Fehlern.
Eine einfache Metaklasse kann dieses Problem lösen:
class Person(metaclass=Validator):
weight = ValidateType(int)
age = ValidateType(int)
name = ValidateType(str)
So würde die Metaklasse aussehen (nicht verwendet, __prepare__
da sie nicht benötigt wird):
class Validator(type):
def __new__(metacls, cls, bases, clsdict):
# search clsdict looking for ValidateType descriptors
for name, attr in clsdict.items():
if isinstance(attr, ValidateType):
attr.name = name
attr.attr = '_' + name
# create final class and return it
return super().__new__(metacls, cls, bases, clsdict)
Ein Probelauf von:
p = Person()
p.weight = 9
print(p.weight)
p.weight = '9'
produziert:
9
Traceback (most recent call last):
File "simple_meta.py", line 36, in <module>
p.weight = '9'
File "simple_meta.py", line 24, in __set__
(self.name, self.type, value))
TypeError: weight must be of type(s) <class 'int'> (got '9')
Hinweis : Dieses Beispiel ist einfach genug, es hätte auch mit einem Klassendekorateur durchgeführt werden können, aber vermutlich würde eine tatsächliche Metaklasse viel mehr bewirken.
Die 'ValidateType'-Klasse als Referenz:
class ValidateType:
def __init__(self, type):
self.name = None # will be set by metaclass
self.attr = None # will be set by metaclass
self.type = type
def __get__(self, inst, cls):
if inst is None:
return self
else:
return inst.__dict__[self.attr]
def __set__(self, inst, value):
if not isinstance(value, self.type):
raise TypeError('%s must be of type(s) %s (got %r)' %
(self.name, self.type, value))
else:
inst.__dict__[self.attr] = value
__set_name__(cls, name)
im Deskriptor ( ValidateType
) den Namen im Deskriptor ( self.name
und in diesem Fall auch self.attr
) festlegen können . Dies wurde hinzugefügt, um für diesen speziellen Anwendungsfall nicht in Metaklassen eintauchen zu müssen (siehe PEP 487).
__call__()
beim Erstellen einer KlasseninstanzWenn Sie Python länger als ein paar Monate programmiert haben, werden Sie schließlich auf Code stoßen, der so 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 aufrufbar verwendet wird. Wie wir aus früheren 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), rufen wir tatsächlich die __call__()
Methode ihrer Metaklasse auf . Zu diesem Zeitpunkt sind die meisten Python-Programmierer etwas verwirrt, da ihnen mitgeteilt wurde, dass Sie beim Erstellen einer solchen Instanz instance = SomeClass()
deren __init__()
Methode aufrufen . Einige, die etwas tiefer gegraben haben, wissen das, bevor __init__()
es das gibt __new__()
. Nun, heute wird eine weitere Ebene der Wahrheit enthüllt, bevor __new__()
es die Metaklasse gibt __call__()
.
Lassen Sie uns die Methodenaufrufkette unter dem Gesichtspunkt der Erstellung einer Instanz einer Klasse untersuchen.
Dies ist eine Metaklasse, die genau in dem Moment protokolliert, bevor eine Instanz erstellt wird und in dem sie zurückgegeben werden soll.
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 die Aufgaben zu protokollieren. Jede Methode delegiert die eigentliche Arbeit an die Implementierung ihres übergeordneten Elements, wodurch das Standardverhalten beibehalten wird. Da die übergeordnete Klasse von type
is Meta_1
( type
die standardmäßige übergeordnete Metaklasse) ist und die Reihenfolge der obigen Ausgabe berücksichtigt wird, haben wir jetzt einen Hinweis darauf, was die Pseudoimplementierung von type.__call__()
:
class type:
def __call__(cls, *args, **kwarg):
# ... maybe a few things done to cls here
# then we call __new__() on the class to create an instance
instance = cls.__new__(cls, *args, **kwargs)
# ... maybe a few things done to the instance here
# then we initialize the instance with its __init__() method
instance.__init__(*args, **kwargs)
# ... maybe a few more things done to instance here
# then we return it
return instance
Wir können sehen, dass die __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 delegiert __init__()
. Es ist auch derjenige, der letztendlich die Instanz zurückgibt.
Aus dem oben Gesagten ergibt sich, dass der Metaklasse __call__()
auch die Möglichkeit gegeben wird, zu entscheiden, ob ein Anruf bei Class_1.__new__()
oder Class_1.__init__()
irgendwann getätigt wird oder nicht . Während der Ausführung könnte tatsächlich ein Objekt zurückgegeben werden, 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__()."
Beobachten wir, was passiert, wenn wiederholt versucht wird, ein Objekt vom Typ zu erstellen Class_2
a = Class_2()
# Meta_2.__call__() before creating an instance.
# Class_2.__new__() before creating instance.
# Class_2.__new__() returning instance.
# entering Class_2.__init__() for initialization.
# exiting Class_2.__init__().
# Meta_2.__call__() returning new instance.
b = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.
c = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.
a is b is c # True
Eine Metaklasse ist eine Klasse, die angibt, wie (einige) andere Klassen erstellt werden sollen.
Dies ist ein Fall, in dem ich Metaklasse als Lösung für mein Problem sah: Ich hatte ein wirklich kompliziertes Problem, das wahrscheinlich anders hätte gelöst werden können, aber ich entschied mich, es mit einer Metaklasse zu lösen. Aufgrund der Komplexität ist es eines der wenigen Module, die ich geschrieben habe, bei denen die Kommentare im Modul die Menge des geschriebenen Codes ü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()
Mit der type(obj)
Funktion erhalten Sie den Typ eines Objekts.
Das type()
einer Klasse ist ihre Metaklasse .
So verwenden Sie eine Metaklasse:
class Foo(object):
__metaclass__ = MyMetaClass
type
ist eine eigene Metaklasse. Die Klasse einer Klasse ist eine Metaklasse - der Hauptteil einer Klasse sind die Argumente, die an die Metaklasse übergeben werden, die zum Erstellen der Klasse verwendet wird.
Hier erfahren Sie, wie Sie mithilfe von Metaklassen die Klassenkonstruktion anpassen.
type
ist eigentlich eine metaclass
- eine Klasse, die andere Klassen erstellt. Die meisten metaclass
sind die Unterklassen von type
. Der metaclass
empfängt die new
Klasse als erstes Argument und bietet Zugriff auf das Klassenobjekt mit den folgenden Details:
>>> class MetaClass(type):
... def __init__(cls, name, bases, attrs):
... print ('class name: %s' %name )
... print ('Defining class %s' %cls)
... print('Bases %s: ' %bases)
... print('Attributes')
... for (name, value) in attrs.items():
... print ('%s :%r' %(name, value))
...
>>> class NewClass(object, metaclass=MetaClass):
... get_choch='dairy'
...
class name: NewClass
Bases <class 'object'>:
Defining class <class 'NewClass'>
get_choch :'dairy'
__module__ :'builtins'
__qualname__ :'NewClass'
Note:
Beachten Sie, dass die Klasse zu keinem Zeitpunkt instanziiert wurde. Der einfache Vorgang des Erstellens der Klasse löste die Ausführung des aus metaclass
.
Python-Klassen sind selbst Objekte - wie zum Beispiel - ihrer Meta-Klasse.
Die Standard-Metaklasse, die angewendet wird, wenn Sie Klassen wie folgt bestimmen:
class foo:
...
Meta-Klassen werden verwendet, um eine Regel auf eine ganze Reihe von Klassen anzuwenden. Angenommen, Sie erstellen ein ORM für den Zugriff auf eine Datenbank und möchten, dass Datensätze aus jeder Tabelle einer Klasse angehören, die dieser Tabelle zugeordnet ist (basierend auf Feldern, Geschäftsregeln usw.), 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 zur Unterstützung von Fremdschlüsseln, die mehrere Klassen von Datensätzen umfasst.
Wenn Sie eine Metaklasse definieren, geben Sie einen Unterklassentyp ein 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 die am häufigsten verwendeten Haken. Metaclassing ist leistungsstark, und oben finden Sie bei weitem keine vollständige Liste der Verwendungszwecke für Metaclassing.
Die Funktion type () 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 (Objekt) 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
Zusätzlich zur Verwendung von type () zum dynamischen Erstellen von Klassen können Sie das Erstellungsverhalten von Klassen steuern und Metaklassen 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 der Typklasse. Das heißt, Typ ist Metaklasse der meisten integrierten Klassen und Metaklasse benutzerdefinierter 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 der Metaklasse übergeben haben. Es gibt den Python-Interpreter an, der die CustomList über ListMetaclass erstellen soll. new (), an dieser Stelle können wir beispielsweise die Klassendefinition ändern, eine neue Methode hinzufügen und dann die überarbeitete Definition zurückgeben.
Zusätzlich zu den veröffentlichten Antworten kann ich sagen, dass a metaclass
das Verhalten für eine Klasse definiert. So können Sie Ihre Metaklasse explizit festlegen. Immer wenn Python ein Schlüsselwort erhält class
, beginnt es nach dem zu suchen metaclass
. Wenn es nicht gefunden wird, wird der Standard-Metaklassentyp verwendet, um das Objekt der Klasse zu erstellen. Mit dem __metaclass__
Attribut können Sie metaclass
Ihre Klasse festlegen :
class MyClass:
__metaclass__ = type
# write here other method
# write here one more method
print(MyClass.__metaclass__)
Die Ausgabe wird folgendermaßen erzeugt:
class 'type'
Und natürlich können Sie Ihre eigenen erstellen metaclass
, um das Verhalten jeder Klasse zu definieren, die mit Ihrer Klasse erstellt wird.
Dazu muss Ihre Standardtypklasse metaclass
geerbt werden, da dies die Hauptklasse 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'
Bei der objektorientierten Programmierung ist eine Metaklasse eine Klasse, deren Instanzen Klassen sind. So wie eine gewöhnliche Klasse das Verhalten bestimmter Objekte definiert, definiert eine Metaklasse das Verhalten bestimmter Klassen und ihrer Instanzen. Der Begriff Metaklasse bedeutet einfach etwas, das zum Erstellen von Klassen verwendet wird. Mit anderen Worten, es ist die Klasse einer Klasse. Die Metaklasse wird verwendet, um die Klasse so zu erstellen, dass eine Klasse eine Instanz einer Metaklasse ist, so wie das Objekt eine Instanz einer Klasse ist. In Python werden Klassen auch als Objekte betrachtet.
Hier ist ein weiteres Beispiel dafür, wofür es verwendet werden kann:
metaclass
die Funktion der Instanz (der Klasse) ändern.class MetaMemberControl(type):
__slots__ = ()
@classmethod
def __prepare__(mcs, f_cls_name, f_cls_parents, # f_cls means: future class
meta_args=None, meta_options=None): # meta_args and meta_options is not necessarily needed, just so you know.
f_cls_attr = dict()
if not "do something or if you want to define your cool stuff of dict...":
return dict(make_your_special_dict=None)
else:
return f_cls_attr
def __new__(mcs, f_cls_name, f_cls_parents, f_cls_attr,
meta_args=None, meta_options=None):
original_getattr = f_cls_attr.get('__getattribute__')
original_setattr = f_cls_attr.get('__setattr__')
def init_getattr(self, item):
if not item.startswith('_'): # you can set break points at here
alias_name = '_' + item
if alias_name in f_cls_attr['__slots__']:
item = alias_name
if original_getattr is not None:
return original_getattr(self, item)
else:
return super(eval(f_cls_name), self).__getattribute__(item)
def init_setattr(self, key, value):
if not key.startswith('_') and ('_' + key) in f_cls_attr['__slots__']:
raise AttributeError(f"you can't modify private members:_{key}")
if original_setattr is not None:
original_setattr(self, key, value)
else:
super(eval(f_cls_name), self).__setattr__(key, value)
f_cls_attr['__getattribute__'] = init_getattr
f_cls_attr['__setattr__'] = init_setattr
cls = super().__new__(mcs, f_cls_name, f_cls_parents, f_cls_attr)
return cls
class Human(metaclass=MetaMemberControl):
__slots__ = ('_age', '_name')
def __init__(self, name, age):
self._name = name
self._age = age
def __getattribute__(self, item):
"""
is just for IDE recognize.
"""
return super().__getattribute__(item)
""" with MetaMemberControl then you don't have to write as following
@property
def name(self):
return self._name
@property
def age(self):
return self._age
"""
def test_demo():
human = Human('Carson', 27)
# human.age = 18 # you can't modify private members:_age <-- this is defined by yourself.
# human.k = 18 # 'Human' object has no attribute 'k' <-- system error.
age1 = human._age # It's OK, although the IDE will show some warnings. (Access to a protected member _age of a class)
age2 = human.age # It's OK! see below:
"""
if you do not define `__getattribute__` at the class of Human,
the IDE will show you: Unresolved attribute reference 'age' for class 'Human'
but it's ok on running since the MetaMemberControl will help you.
"""
if __name__ == '__main__':
test_demo()
Das metaclass
ist mächtig, es gibt viele Dinge (wie Affenmagie), die Sie damit machen können, aber seien Sie vorsichtig, dies ist möglicherweise nur Ihnen bekannt.
Eine Klasse in Python ist ein Objekt und wie jedes andere Objekt eine Instanz von "etwas". Dieses "Etwas" wird als Metaklasse bezeichnet. Diese Metaklasse ist ein spezieller Klassentyp, der Objekte anderer Klassen erstellt. Daher ist die Metaklasse für die Erstellung neuer Klassen verantwortlich. Auf diese Weise kann der Programmierer die Art und Weise anpassen, wie Klassen generiert werden.
Um eine Metaklasse zu erstellen, werden normalerweise die Methoden new () und init () überschrieben . new () kann überschrieben werden, um die Art und Weise zu ändern, wie Objekte erstellt werden, während init () überschrieben werden kann, um die Art und Weise der Initialisierung des Objekts zu ändern. Metaklasse kann auf verschiedene Arten erstellt werden. Eine Möglichkeit besteht darin, die Funktion type () zu verwenden. Die Funktion type () erstellt beim Aufruf mit 3 Parametern eine Metaklasse. Die Parameter sind: -
Eine andere Möglichkeit zum Erstellen einer Metaklasse besteht aus dem Schlüsselwort "Metaklasse". Definieren Sie die Metaklasse als einfache Klasse. Übergeben Sie in den Parametern der geerbten Klasse metaclass = metaclass_name
Metaklasse kann speziell in den folgenden Situationen verwendet werden: -
Beachten Sie, dass in Python 3.6 eine neue Dunder-Methode __init_subclass__(cls, **kwargs)
eingeführt wurde, um viele gängige Anwendungsfälle für Metaklassen zu ersetzen. Is wird aufgerufen, wenn eine Unterklasse der definierenden Klasse erstellt wird. Siehe Python-Dokumente .
Metaklasse ist eine Art Klasse, die definiert, wie sich die Klasse verhält, oder wir können sagen, dass eine Klasse selbst eine Instanz einer Metaklasse ist.
class A(type):pass<NEWLINE>class B(type,metaclass=A):pass<NEWLINE>b.__class__ = b