2013-08-08 2 views
60

Gibt es eine Möglichkeit, Code auszulösen, wenn meine Klasse unterklassifiziert ist?Python: Code ausführen, wenn eine Klasse unterklassifiziert ist

class SuperClass: 
    def triggered_routine(subclass): 
     print("was subclassed by " + subclass.__name__) 

magically_register_triggered_routine() 

print("foo") 

class SubClass0(SuperClass): 
    pass 

print("bar") 

class SubClass1(SuperClass): 
    print("test") 

Sollte Ausgabe

foo 
was subclassed by SubClass0 
bar 
test 
was subclassed by SubClass1 
+1

Verwenden Sie eine Metaklasse; Metaklassen werden beim Erstellen von Klassen aufgerufen, genauso wie Klassen beim Erstellen von Instanzen aufgerufen werden. –

+2

Kann keine Antwort hinzufügen, aber python3.6 hat heute die '__init_subclass__' - check it out! –

+1

@OrDuan: Danke, klingt nützlich. Vielleicht sogar Grund genug, um diese Frage ein Duplikat zu markieren, da es jetzt eine dedizierte Lösung für mein Problem statt der "Verwendung einer Metaklasse" gibt. –

Antwort

39

Klassen (Standard) sind Instanzen von type. So wie eine Instanz einer Klasse Foo von foo = Foo(...), erstellt wird, wird eine Instanz type (d. H. Eine Klasse) von myclass = type(name, bases, clsdict) erstellt.

Wenn Sie möchten, dass im Moment der Klassenerstellung etwas Besonderes passiert, dann müssen Sie das, was die Klasse erzeugt, ändern - also type. Der Weg, dies zu tun, besteht darin, eine Unterklasse von type zu definieren - d. H. Eine Metaklasse.

Eine Metaklasse ist zu ihrer Klasse wie eine Klasse zu ihrer Instanz.

In Python2 würden Sie die Metaklasse einer Klasse definieren mit

class SuperClass: 
    __metaclass__ = Watcher 

wo Watcher eine Unterklasse von type ist.

In Python3 die Syntax

class SuperClass(metaclass=Watcher) 

Beide

Superclass = Watcher(name, bases, clsdict) 

, wo in diesem Fall zu

äquivalent sind geändert worden ist, die Zeichenfolge name 'Superclass' entspricht, und ist das Tupel bases (object,). Das clsdict ist ein Wörterbuch der Klassenattribute, die im Hauptteil der Klassendefinition definiert sind.

Beachten Sie die Ähnlichkeit zu myclass = type(name, bases, clsdict).

So, wie Sie __init__ eine Klasse verwenden würden Ereignisse im Moment einer Schöpfung der Instanz zu steuern, können Sie Ereignisse zur Zeit einer Schöpfung der Klasse Steuerung mit einer Metaklasse der __init__:


class Watcher(type): 
    def __init__(cls, name, bases, clsdict): 
     if len(cls.mro()) > 2: 
      print("was subclassed by " + name) 
     super(Watcher, cls).__init__(name, bases, clsdict) 

class SuperClass: 
    __metaclass__ = Watcher 


print("foo") 

class SubClass0(SuperClass): 
    pass 

print("bar") 

class SubClass1(SuperClass): 
    print("test") 

druckt

foo 
was subclassed by SubClass0 
bar 
test 
was subclassed by SubClass1 
+2

Danke. Ihr Code funktioniert jedoch nur in python2. In python3 müsste SuperClass 'class SuperClass (metaclass = Watcher) sein: pass ' –

+0

Danke. Ich habe den Beitrag bearbeitet, um den Unterschied in Python2 und Python3 Syntax zu adressieren. – unutbu

+1

Zwei wichtige Dinge, die ich vermisste, als ich das selbst ausprobierte: 1 - 'Watcher' Subklassen' type', nicht 'object'. 2 - 'SuperClass' hat überhaupt keine Superklasse, nicht' Objekt'. Ich bin einfach so daran gewöhnt, "object" als eine Oberklasse für jede Klasse, die ich definiert habe, aufzuschreiben, dass ich es irgendwie übersehen habe, dass dieser Code etwas anderes verwendet hat. Obwohl es anscheinend keinen Schaden gibt, wenn "SuperClass" von "Objekt" geerbt wird, ist es definitiv notwendig, dass "Watcher" von "Typ", nicht von "Objekt" erbt. – ArtOfWarfare

6

Bearbeiten: Mein alter Beitrag funktionierte tatsächlich nicht. Subclassing von funktioniert nicht wie erwartet.

Zuerst möchten wir eine Möglichkeit haben, der Metaklasse zu sagen, dass diese spezielle Methode das spezielle Unterklassenverhalten haben soll, wir setzen einfach ein Attribut auf die Funktion, die wir aufrufen möchten. Als Annehmlichkeit werden wir die Funktion sogar in eine umwandeln, so dass die echte Basisklasse, in der sie gefunden wurde, ebenfalls entdeckt werden kann. Wir geben die Klassenmethode zurück, so dass sie als Dekorateur verwendet werden kann, was am bequemsten ist.

import types 
import inspect 

def subclass_hook(func): 
    func.is_subclass_hook = True 
    return classmethod(func) 

Wir werden auch eine bequeme Möglichkeit, wollen zu sehen, dass die subclass_hook Dekorateur verwendet wurde. Wir wissen, dass verwendet wurde, also prüfen wir das und suchen dann nur nach dem is_subclass_hook Attribut.

def test_subclass_hook(thing): 
    x = (isinstance(thing, types.MethodType) and 
     getattr(thing.im_func, 'is_subclass_hook', False)) 
    return x 

Schließlich brauchen wir eine Metaklasse, die auf den Informationen fungiert: Bei den meisten Fällen das Interessanteste, was hier zu tun, nur jeder der mitgelieferten Basen für Haken prüfen wird. Auf diese Weise funktioniert Super auf die am wenigsten überraschende Art und Weise.

class MyMetaclass(type): 
    def __init__(cls, name, bases, attrs): 
     super(MyMetaclass, cls).__init__(name, bases, attrs) 

     for base in bases: 
      if base is object: 
       continue 
      for name, hook in inspect.getmembers(base, test_subclass_hook): 
       hook(cls) 

und das sollte es tun.

>>> class SuperClass: 
...  __metaclass__ = MyMetaclass 
...  @subclass_hook 
...  def triggered_routine(cls, subclass): 
...   print(cls.__name__ + " was subclassed by " + subclass.__name__) 

>>> class SubClass0(SuperClass): 
...  pass 
SuperClass was subclassed by SubClass0 

>>> class SubClass1(SuperClass): 
...  print("test") 
test 
SuperClass was subclassed by SubClass1 
Verwandte Themen