Es ist schwer zu sagen, was Sie wirklich wollen. Aber eine Metaklasse, die sich bei jeder neuen Klasse eine Eigenschaft hinzufügt, funktioniert vielleicht besser für alles, was Sie wollen.
Soweit ich Ihren Code verstehen kann, werden ältere Klassen nicht mit Verweisen auf die neueren Klassen gefüllt, da Sie neue Instanzen erstellen (die wiederum die Referenz für andere erhalten).
Auf einer zweiten aber dinamically Schaffung Eigenschaften inisde __new__
scheint hacky - aber man kann einfach die Metaklasse __getattr__
und __dir__
Methoden für viel weniger gewundenen Code implementieren:
Die einfache Version für Klassen funktioniert, aber nicht für ihre Instanzen - weil Instanzen auslösen nicht die __getattr__
auf der Metaklasse:
class Pipeable(type):
_classes = {}
def __new__(metacls, name, bases, namespace, **kwds):
cls = type.__new__(metacls, name, bases, namespace)
metacls._classes[name] = cls
return cls
def __getattr__(cls, attr):
classes = cls.__class__._classes
if attr not in classes:
raise AttributeError
def pipe_within(*args, **kwargs):
return cls(*args, op=classes[attr], **kwargs)
print('piping...')
return pipe_within
def __dir__(cls):
regular = super().__dir__()
return sorted(regular + list(cls.__class__._classes.keys()))
class Op(metaclass=Pipeable):
def __init__(self, op=None):
if op is not None:
print('piped!')
self.op = op
Op.Op()
(auch beachten, dass im Laufe der Zeit, die ich diesen Parameter nahm Namenskonvention auf metaclasses verwenden - wie die meisten ihrer Methoden nehmen die Klasse erstellt mit Anstelle des "Selbst" in normalen Klassen finde ich, dass diese Benennung einfacher zu folgen ist. Es ist nicht zwingend erforderlich, aber nicht unbedingt "richtig", aber)
Aber dann können wir es für Instanzen arbeiten, indem Sie die __dir__
und __getattr__
direkt auf den erstellten Klassen auch erstellen. Der Haken dabei ist, dass die Klasse, die Sie erstellen, bereits eine __getattr__
oder benutzerdefinierte __dir__
haben, sogar in ihren Super-Klassen, die umgebrochen werden müssen. Und dann wollen wir nicht unsere eigene neu wickeln __dir__
und __getattr__
, so einige zusätzliche Pflege:
class Pipeable(type):
_classes = {}
def __new__(metacls, name, bases, namespace, **kwds):
cls = type.__new__(metacls, name, bases, namespace)
metacls._classes[name] = cls
original__getattr__ = getattr(cls, "__getattr__", None)
if hasattr(original__getattr__, "_metapipping"):
# Do not wrap our own (metaclass) implementation of __getattr__
original__getattr__ = None
original__dir__ = getattr(cls, "__dir__") # Exists in "object", so it is always found.
# these two functions have to be nested so they can get the
# values for the originals "__getattr__" and "__dir__" from
# the closure. These values could be set on the class created, alternatively.
def __getattr__(self, attr):
if original__getattr__:
# If it is desired that normal attribute lookup have
# less precedence than these injected operators
# move this "if" block down.
try:
value = original__getattr__(self, attr)
except AttributeError:
pass
else:
return value
classes = self.__class__.__class__._classes
if attr not in classes:
raise AttributeError
def pipe_within(*args, **kwargs):
return cls(*args, op=classes[attr], **kwargs)
print('piping...')
return pipe_within
__getattr__._pipping = True
def __dir__(self):
regular = original__dir__(self)
return sorted(regular + list(self.__class__.__class__._classes.keys()))
__dir__.pipping = True
if not original__getattr__ or not hasattr(original__getattr__, "_pipping"):
cls.__getattr__ = __getattr__
if not hasattr(original__dir__, "_pipping"):
cls.__dir__ = __dir__
return cls
def __getattr__(cls, attr):
classes = cls.__class__._classes
if attr not in classes:
raise AttributeError
def pipe_within(*args, **kwargs):
return cls(*args, op=classes[attr], **kwargs)
print('piping...')
return pipe_within
__getattr__._metapipping = True
def __dir__(cls):
regular = super().__dir__()
return sorted(regular + list(cls.__class__._classes.keys()))
class Op(metaclass=Pipeable):
def __init__(self, op=None):
if op is not None:
print('piped!')
Op().Op()
Also, das lange endete als - aber es „das Richtige tut“, indem sichergestellt wird Alle Klassen und Instanzen in der Hierarchie können sich unabhängig von der Erstellungsreihenfolge sehen.
Auch, was die Komplexität bilden wird, um andere mögliche Anpassungen von __getattr__
und __dir__
in der Klassenhierarchie korrekt Einwickeln - wenn Sie keine Anpassung von denen bekommen, das eine Größenordnung einfacher sein kann:
class Pipeable(type):
_classes = {}
def __new__(metacls, name, bases, namespace, **kwds):
cls = type.__new__(metacls, name, bases, namespace)
metacls._classes[name] = cls
def __getattr__(self, attr):
classes = self.__class__.__class__._classes
if attr not in classes:
raise AttributeError
def pipe_within(*args, **kwargs):
return cls(*args, op=classes[attr], **kwargs)
print('piping...')
return pipe_within
def __dir__(self):
regular = original__dir__(self)
return sorted(regular + list(self.__class__.__class__._classes.keys()))
cls.__getattr__ = __getattr__
cls.__dir__ = __dir__
return cls
def __getattr__(cls, attr):
classes = cls.__class__._classes
if attr not in classes:
raise AttributeError
def pipe_within(*args, **kwargs):
return cls(*args, op=classes[attr], **kwargs)
print('piping...')
return pipe_within
def __dir__(cls):
regular = super().__dir__()
return sorted(regular + list(cls.__class__._classes.keys()))
Deskriptoren müssen auf die Klasse, nicht die Instanz gehen. – user2357112