Ein Klassenblock ist in etwa syntaktischer Zucker zum Erstellen eines Wörterbuchs und zum Aufrufen einer Metaklasse zum Erstellen des Klassenobjekts.
Dieses:
class Foo(object):
__metaclass__ = FooMeta
FOO = 123
def a(self):
pass
Kommt ziemlich aus, als ob Sie geschrieben hatte:
d = {}
d['__metaclass__'] = FooMeta
d['FOO'] = 123
def a(self):
pass
d['a'] = a
Foo = d.get('__metaclass__', type)('Foo', (object,), d)
Nur ohne den Namensraum Umweltverschmutzung (und in Wirklichkeit gibt es auch eine Suche durch alle Basen zu bestimmen die Metaklasse, oder ob es einen Metaklassenkonflikt gibt, aber ich ignoriere das hier).
Die metaclass' __setattr__
kann kontrollieren, was passiert, wenn Sie versuchen, auf einer seiner Instanzen ein Attribut gesetzt (das Klassenobjekt), aber innerhalb der Klasse Block Sie nicht, das zu tun, sind Sie in ein Einfügen Wörterbuchobjekt, so dass die dict
Klasse steuert, was vor sich geht, nicht Ihre Metaklasse. Du hast also kein Glück.
Es sei denn, Sie verwenden Python 3.x! In Python 3.x können Sie eine __prepare__
Klassenmethode (oder staticmethod) für eine Metaklasse definieren, die steuert, welches Objekt zum Sammeln von Attributen verwendet wird, die in einem Klassenblock gesetzt sind, bevor sie an den Metaklassenkonstruktor übergeben werden. Der Standard __prepare__
gibt einfach ein normales Wörterbuch, aber man konnte eine benutzerdefinierte dict-ähnliche Klasse bauen, die keine Schlüssel erlauben neu definiert werden, und dass Ihre Attribute zu akkumulieren verwenden:
from collections import MutableMapping
class SingleAssignDict(MutableMapping):
def __init__(self, *args, **kwargs):
self._d = dict(*args, **kwargs)
def __getitem__(self, key):
return self._d[key]
def __setitem__(self, key, value):
if key in self._d:
raise ValueError(
'Key {!r} already exists in SingleAssignDict'.format(key)
)
else:
self._d[key] = value
def __delitem__(self, key):
del self._d[key]
def __iter__(self):
return iter(self._d)
def __len__(self):
return len(self._d)
def __contains__(self, key):
return key in self._d
def __repr__(self):
return '{}({!r})'.format(type(self).__name__, self._d)
class RedefBlocker(type):
@classmethod
def __prepare__(metacls, name, bases, **kwargs):
return SingleAssignDict()
def __new__(metacls, name, bases, sad):
return super().__new__(metacls, name, bases, dict(sad))
class Okay(metaclass=RedefBlocker):
a = 1
b = 2
class Boom(metaclass=RedefBlocker):
a = 1
b = 2
a = 3
des Lauf gibt mir:
Traceback (most recent call last):
File "/tmp/redef.py", line 50, in <module>
class Boom(metaclass=RedefBlocker):
File "/tmp/redef.py", line 53, in Boom
a = 3
File "/tmp/redef.py", line 15, in __setitem__
'Key {!r} already exists in SingleAssignDict'.format(key)
ValueError: Key 'a' already exists in SingleAssignDict
Einige Anmerkungen:
__prepare__
hat eine classmethod
oder staticmethod
, sein, weil es vor th genannt zu werden ist Die Metaklasseninstanz (Ihre Klasse) existiert.
type
noch braucht, um seine dritte Parameter eine echte dict
zu sein, so haben Sie eine __new__
Methode haben, der die SingleAssignDict
zu einem normalen
- wandelt ich
dict
subclassed hätte, die wahrscheinlich vermieden hätte (2), aber Ich mag das wirklich nicht, weil die nicht-grundlegenden Methoden wie update
Ihre Überschreibungen der grundlegenden Methoden wie __setitem__
nicht respektieren. Daher bevorzuge ich die Unterklasse collections.MutableMapping
und wickle ein Wörterbuch ein.
- Das eigentliche
Okay.__dict__
Objekt ist ein normales Wörterbuch, weil es von type
festgelegt wurde und type
ist knifflig über die Art von Wörterbuch, das es will. Dies bedeutet, dass das Überschreiben von Klassenattributen nach der Klassenerstellung keine Ausnahme auslöst. Sie können das Attribut nach dem Oberklassenaufruf in __new__
überschreiben, wenn Sie das vom Klassenobjekt erzwungene no-overwriting beibehalten möchten.
Leider ist diese Technik in Python 2.x nicht verfügbar (I geprüft). Die Methode __prepare__
wird nicht aufgerufen, was sinnvoll ist, da in Python 2.x die Metaklasse durch das magische Attribut __metaclass__
und nicht durch ein spezielles Schlüsselwort im Klassenblock bestimmt wird. Das bedeutet, dass das dict-Objekt, das zum Sammeln von Attributen für den Klassenblock verwendet wird, bereits existiert, wenn die Metaklasse bekannt ist.
Vergleichen Python 2:
class Foo(object):
__metaclass__ = FooMeta
FOO = 123
def a(self):
pass
Sein entspricht in etwa:
d = {}
d['__metaclass__'] = FooMeta
d['FOO'] = 123
def a(self):
pass
d['a'] = a
Foo = d.get('__metaclass__', type)('Foo', (object,), d)
Wo die Metaklasse wird aus dem Wörterbuch bestimmt aufzurufen, im Vergleich zu Python 3:
class Foo(metaclass=FooMeta):
FOO = 123
def a(self):
pass
Being grob entspricht:
d = FooMeta.__prepare__('Foo',())
d['Foo'] = 123
def a(self):
pass
d['a'] = a
Foo = FooMeta('Foo',(), d)
Wo das zu verwendende Wörterbuch aus der Metaklasse ermittelt wird.
Dieses genaue Problem ist der Grund, warum metaclass Syntax in py3 geändert hat! – SingleNegationElimination