Das einzige, was für eine Metaklasse benötigt wird, ist eigentlich ganz einfach: genau das Erstellen der intances
und all
Attribute.
Alles, was Sie tun müssen, ist, diese in den Namensraum einzufügen. Ah, es muss auch die Klasse __new__
Methode umhüllen, um neue Instanzen in die instances
Liste einzufügen.
Der Teil, der das gewünschte Verhalten von all
ist, ist interessant, und das kann mit dem Deskriptor-Protokoll implementiert werden, und Attribut Zugriffskontrolle, so müssen wir ein paar spezielle Klassen, die die entsprechenden Objekte zurückgeben, wenn angefordert nach das ".".
"All" ist die Klasse, die als "alle" instanziiert wird - es benötigt nur eine -Methode, um ein anderes spezielles Objekt aus der AllAttr
-Klasse zurückzugeben, das bereits an die Elternklasse gebunden ist.
"AllAttr" ist ein spezielles Objekt, das bei jedem Attributzugriff Ihre Anforderungen an die Mitglieder des Attributs "instance" der Eigentümerklasse erfüllt.
Und "CallAllList" ist eine spezielle Liste Unterklasse, die aufrufbar ist, und ruft alle seine Mitglieder nacheinander. Es wird von AllAttr verwendet, wenn das erforderliche Attribut der Besitzerklasse selbst aufrufbar ist.
class CallAllList(list):
def __call__(self, *args, **kwargs):
return [instance(*args, **kwargs) for instance in self]
class AllAttr(object):
def __init__(self, owner):
self._owner = owner
def __getattr__(self, attr):
method = getattr(self._owner, attr, None)
cls = CallAllList if callable(method) else list
return cls(getattr(obj, attr) for obj in self._owner.instances)
def __setattr__(self, attr, value):
if attr == "_owner":
return super(AllAttr, self).__setattr__(attr, value)
for obj in self._owner.instances:
setattr(obj, attr, value)
class All(object):
def __get__(self, instance, owner):
return AllAttr(owner)
def __repr__(self):
return "Representation of all instances of '{}'".format(self.__class__.__name__)
class MetaAll(type):
def __new__(metacls, name, bases, namespace):
namespace["all"] = All()
namespace["instances"] = []
cls = super(MetaAll, metacls).__new__(metacls, name, bases, namespace)
original_new = getattr(cls, "__new__")
def __new__(cls, *args, **kwargs):
instance = original_new(cls, *args, **kwargs)
cls.instances.append(instance)
return instance
cls.__new__ = __new__
return cls
class Foo(metaclass=MetaAll):
pass
Der obige Code ist so geschrieben, dass es Python 3 und Python 2 kompatibel ist, da man immer noch zu sein scheint am Beispiel Python2 gegeben Ihres „Drucks“. Die einzige Sache, die nicht kompatibel mit beiden Formen geschrieben werden kann, ist die Metaklasse mit Deklaration selbst - einfach eine __metaclass__ = MetaAll
im Körper der Foo-Klasse deklarieren, wenn Sie Python 2 verwenden. Aber Sie sollten nicht wirklich Python2 verwenden, nur zu Python wechseln 3 sobald du kannst.
Update
Es kommt vor, dass Python 2 die „ungebundene Methode“ Figur hat, und das spezielle Gehäuse von __new__
wie 3 in Python nicht funktioniert: Sie haben eine Funktion __new__
auf die Klasse mit dem Namen nicht nur Attribut können. Um die korrekte __new__
Methode aus den Oberklassen zu erhalten, ist der einfachste Weg, eine wegwerfbare Klasse zu erstellen, so dass sie linear gesucht werden kann. Andernfalls müsste man den MRO-Algorithmus neu implementieren, um die richtige __new__
Methode zu erhalten.
Also, für Python 2, sollte die Metaklasse sein:
class MetaAll(type):
def __new__(metacls, name, bases, namespace):
namespace["all"] = All()
namespace["instances"] = []
if "__new__" in namespace:
original_new = namespace["__new__"]
def __new__(cls, *args, **kwargs):
instance = original_new(cls, *args, **kwargs)
cls.instances.append(instance)
return instance
else:
# We create a disposable class just to get the '__mro__'
stub_cls = super(MetaAll, metacls).__new__(metacls, name, bases, {})
for parent in stub_cls.__mro__[1:]:
if "__new__" in parent.__dict__:
original_new = parent.__dict__["__new__"]
break
def __new__(cls, *args, **kwargs):
instance = original_new(cls, *args, **kwargs)
cls.instances.append(instance)
return instance
namespace["__new__"] = __new__
final_cls = super(MetaAll, metacls).__new__(metacls, name, bases, namespace)
return final_cls
class Foo(object):
__metaclass__ = MetaAll
(. Jetzt wieder dieses Ding ist alt Just for Python siedeln 3.6)
Sorgfalt zu füllen, wie dies verhalten soll, wenn Subklassen „Foo“? Sollen die Unterklassen auch Teil von Foo's Instanzen sein oder für jede Unterklasse ein leeres Instace-Array? – jsbueno
So ist die einfachste Sache, dass jede Unterklasse ihr eigenes "Instanzen" -Attribut hat, und das ist es, was die Antwort, die ich gepostet habe, tut. – jsbueno
Ja, das ist das Verhalten, das ich wollte, danke. Sobald ich Zugriff auf einen Computer habe (ich benutze gerade mein Telefon), werde ich deine Antwort annehmen und sie annehmen, um das Programm zu testen. –