Vor kurzem wurde mir dieselbe Frage gestellt, und ich fand mehrere Antworten. Ich hoffe, es ist in Ordnung, diesen Thread wiederzubeleben, da ich einige der genannten Anwendungsfälle näher erläutern und einige neue hinzufügen wollte.
Die meisten Metaklassen, die ich gesehen habe, machen eines von zwei Dingen:
Registrierung (Hinzufügen einer Klasse zu einer Datenstruktur):
models = {}
class ModelMetaclass(type):
def __new__(meta, name, bases, attrs):
models[name] = cls = type.__new__(meta, name, bases, attrs)
return cls
class Model(object):
__metaclass__ = ModelMetaclass
Wann immer Sie eine Unterklasse haben Model
, wird Ihre Klasse im models
Wörterbuch registriert :
>>> class A(Model):
... pass
...
>>> class B(A):
... pass
...
>>> models
{'A': <__main__.A class at 0x...>,
'B': <__main__.B class at 0x...>}
Dies kann auch mit Klassendekorateuren durchgeführt werden:
models = {}
def model(cls):
models[cls.__name__] = cls
return cls
@model
class A(object):
pass
Oder mit einer expliziten Registrierungsfunktion:
models = {}
def register_model(cls):
models[cls.__name__] = cls
class A(object):
pass
register_model(A)
Eigentlich ist das so ziemlich das Gleiche: Sie erwähnen Klassendekorateure ungünstig, aber es ist wirklich nichts anderes als syntaktischer Zucker für einen Funktionsaufruf für eine Klasse, also gibt es keine Magie.
Der Vorteil von Metaklassen ist in diesem Fall die Vererbung, da sie für alle Unterklassen funktionieren, während die anderen Lösungen nur für Unterklassen funktionieren, die explizit dekoriert oder registriert sind.
>>> class B(A):
... pass
...
>>> models
{'A': <__main__.A class at 0x...> # No B :(
Refactoring (Ändern von Klassenattributen oder Hinzufügen neuer Attribute):
class ModelMetaclass(type):
def __new__(meta, name, bases, attrs):
fields = {}
for key, value in attrs.items():
if isinstance(value, Field):
value.name = '%s.%s' % (name, key)
fields[key] = value
for base in bases:
if hasattr(base, '_fields'):
fields.update(base._fields)
attrs['_fields'] = fields
return type.__new__(meta, name, bases, attrs)
class Model(object):
__metaclass__ = ModelMetaclass
Wenn Sie Model
einige Field
Attribute unterordnen und definieren , werden ihnen ihre Namen hinzugefügt (z. B. für informativere Fehlermeldungen) und in einem _fields
Wörterbuch zusammengefasst (für eine einfache Iteration, ohne dass alle Klassenattribute und alle ihre Basisklassen durchsucht werden müssen.) Attribute jedes Mal):
>>> class A(Model):
... foo = Integer()
...
>>> class B(A):
... bar = String()
...
>>> B._fields
{'foo': Integer('A.foo'), 'bar': String('B.bar')}
Auch dies kann (ohne Vererbung) mit einem Klassendekorateur durchgeführt werden:
def model(cls):
fields = {}
for key, value in vars(cls).items():
if isinstance(value, Field):
value.name = '%s.%s' % (cls.__name__, key)
fields[key] = value
for base in cls.__bases__:
if hasattr(base, '_fields'):
fields.update(base._fields)
cls._fields = fields
return cls
@model
class A(object):
foo = Integer()
class B(A):
bar = String()
# B.bar has no name :(
# B._fields is {'foo': Integer('A.foo')} :(
Oder explizit:
class A(object):
foo = Integer('A.foo')
_fields = {'foo': foo} # Don't forget all the base classes' fields, too!
Obwohl dies im Gegensatz zu Ihrer Befürwortung einer lesbaren und wartbaren Nicht-Meta-Programmierung viel umständlicher, redundanter und fehleranfälliger ist:
class B(A):
bar = String()
# vs.
class B(A):
bar = String('bar')
_fields = {'B.bar': bar, 'A.foo': A.foo}
Nachdem Sie die häufigsten und konkretesten Anwendungsfälle berücksichtigt haben, müssen Sie nur dann unbedingt Metaklassen verwenden, wenn Sie den Klassennamen oder die Liste der Basisklassen ändern möchten, da diese Parameter nach ihrer Definition in die Klasse eingebrannt werden und kein Dekorator vorhanden ist oder Funktion kann sie aufbacken.
class Metaclass(type):
def __new__(meta, name, bases, attrs):
return type.__new__(meta, 'foo', (int,), attrs)
class Baseclass(object):
__metaclass__ = Metaclass
class A(Baseclass):
pass
class B(A):
pass
print A.__name__ # foo
print B.__name__ # foo
print issubclass(B, A) # False
print issubclass(B, int) # True
Dies kann in Frameworks nützlich sein, um Warnungen auszugeben, wenn Klassen mit ähnlichen Namen oder unvollständigen Vererbungsbäumen definiert sind, aber ich kann mir keinen Grund neben dem Trolling vorstellen, diese Werte tatsächlich zu ändern. Vielleicht kann David Beazley.
Wie auch immer, in Python 3 haben Metaklassen auch die __prepare__
Methode, mit der Sie den Klassenkörper in einer anderen Zuordnung als a auswerten können dict
, um geordnete Attribute, überladene Attribute und andere böse coole Dinge zu unterstützen:
import collections
class Metaclass(type):
@classmethod
def __prepare__(meta, name, bases, **kwds):
return collections.OrderedDict()
def __new__(meta, name, bases, attrs, **kwds):
print(list(attrs))
# Do more stuff...
class A(metaclass=Metaclass):
x = 1
y = 2
# prints ['x', 'y'] rather than ['y', 'x']
class ListDict(dict):
def __setitem__(self, key, value):
self.setdefault(key, []).append(value)
class Metaclass(type):
@classmethod
def __prepare__(meta, name, bases, **kwds):
return ListDict()
def __new__(meta, name, bases, attrs, **kwds):
print(attrs['foo'])
# Do more stuff...
class A(metaclass=Metaclass):
def foo(self):
pass
def foo(self, x):
pass
# prints [<function foo at 0x...>, <function foo at 0x...>] rather than <function foo at 0x...>
Sie könnten argumentieren, dass geordnete Attribute mit Erstellungszählern erreicht werden können und das Überladen mit Standardargumenten simuliert werden kann:
import itertools
class Attribute(object):
_counter = itertools.count()
def __init__(self):
self._count = Attribute._counter.next()
class A(object):
x = Attribute()
y = Attribute()
A._order = sorted([(k, v) for k, v in vars(A).items() if isinstance(v, Attribute)],
key = lambda (k, v): v._count)
class A(object):
def _foo0(self):
pass
def _foo1(self, x):
pass
def foo(self, x=None):
if x is None:
return self._foo0()
else:
return self._foo1(x)
Es ist nicht nur viel hässlicher, sondern auch weniger flexibel: Was ist, wenn Sie geordnete Literalattribute wie Ganzzahlen und Zeichenfolgen möchten? Was ist, wenn None
ein gültiger Wert für ist x
?
Hier ist ein kreativer Weg, um das erste Problem zu lösen:
import sys
class Builder(object):
def __call__(self, cls):
cls._order = self.frame.f_code.co_names
return cls
def ordered():
builder = Builder()
def trace(frame, event, arg):
builder.frame = frame
sys.settrace(None)
sys.settrace(trace)
return builder
@ordered()
class A(object):
x = 1
y = 'foo'
print A._order # ['x', 'y']
Und hier ist ein kreativer Weg, um den zweiten zu lösen:
_undefined = object()
class A(object):
def _foo0(self):
pass
def _foo1(self, x):
pass
def foo(self, x=_undefined):
if x is _undefined:
return self._foo0()
else:
return self._foo1(x)
Aber das ist viel, VIEL Voodoo-er als eine einfache Metaklasse (besonders die erste, die Ihr Gehirn wirklich zum Schmelzen bringt). Mein Punkt ist, dass Sie Metaklassen als ungewohnt und kontraintuitiv betrachten, aber Sie können sie auch als nächsten Schritt der Evolution in Programmiersprachen betrachten: Sie müssen nur Ihre Denkweise anpassen. Schließlich könnten Sie wahrscheinlich alles in C tun, einschließlich der Definition einer Struktur mit Funktionszeigern und der Übergabe als erstes Argument an ihre Funktionen. Eine Person, die C ++ zum ersten Mal sieht, könnte sagen: "Was ist diese Magie? Warum übergibt der Compiler implizitthis
zu Methoden, aber nicht zu regulären und statischen Funktionen? Es ist besser, explizit und ausführlich über Ihre Argumente zu sprechen. "Aber dann ist objektorientierte Programmierung viel leistungsfähiger, wenn Sie sie erhalten, und dies ist auch ... ähm ... quasi-aspektorientierte Programmierung, denke ich. Und wenn Sie es einmal tun Metaklassen verstehen, sie sind eigentlich sehr einfach. Warum also nicht verwenden, wenn es Ihnen passt?
Und schließlich sind Metaklassen radikal und das Programmieren sollte Spaß machen. Die ständige Verwendung von Standardprogrammierkonstrukten und Entwurfsmustern ist langweilig und wenig inspirierend und behindert Ihre Vorstellungskraft. Lebe ein bisschen! Hier ist eine Metametaklasse, nur für dich.
class MetaMetaclass(type):
def __new__(meta, name, bases, attrs):
def __new__(meta, name, bases, attrs):
cls = type.__new__(meta, name, bases, attrs)
cls._label = 'Made in %s' % meta.__name__
return cls
attrs['__new__'] = __new__
return type.__new__(meta, name, bases, attrs)
class China(type):
__metaclass__ = MetaMetaclass
class Taiwan(type):
__metaclass__ = MetaMetaclass
class A(object):
__metaclass__ = China
class B(object):
__metaclass__ = Taiwan
print A._label # Made in China
print B._label # Made in Taiwan
Bearbeiten
Dies ist eine ziemlich alte Frage, aber sie wird immer noch positiv bewertet, daher dachte ich, ich würde einen Link zu einer umfassenderen Antwort hinzufügen. Wenn Sie möchten mehr über metaclasses und ihre Verwendung lesen, ich habe gerade einen Artikel darüber veröffentlicht hier .