2017-11-23 4 views
1

Ich habe Klasse Op:Warum wird die Methode __get__ der Metaklasse nicht aufgerufen?

class Pipeable(type): 
    def __get__(self, instance, owner): 
     def pipe_within(*args, **kwargs): 
      return self(*args, op=instance, **kwargs) 
     print('piping...') 
     return pipe_within 

class Op(metaclass=Pipeable): 
    def __init__(self, op=None): 
     if op is not None: 
      print('piped!') 
     self.op = op 
     self.__dict__[type(self).__name__] = type(self) 

Ich erwarte Op Klasse selbst als Beschreiber zu arbeiten, weil seine Metaklasse __get__ Methode hat, aber der Code

op = Op().Op() 

nicht Op.__get__ aufrufen. Warum?

+1

Deskriptoren müssen auf die Klasse, nicht die Instanz gehen. – user2357112

Antwort

0

Um in Arbeit zu kommen, muss der Deskriptor ein Klassenattribut sein, nicht das der Instanz. Dieser Code macht, was gewünscht wurde.

class Pipeable(type): 
    _instances = {} 

    def __new__(cls, name, bases, namespace, **kwds): 
     namespace.update(cls._instances) 
     instance = type.__new__(cls, name, bases, namespace) 

     cls._instances[name] = instance 
     for inst in cls._instances: 
      setattr(inst, name, instance) 
     return instance 

    def __get__(self, instance, owner): 
     def pipe_within(*args, **kwargs): 
      return self(*args, op=instance, **kwargs) 
     print('piping...') 
     return pipe_within 


class Op(metaclass=Pipeable): 
    def __init__(self, op=None): 
     if op is not None: 
      print('piped!') 
     self.op = op 

Op().Op() 
1

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())) 
Verwandte Themen