2017-04-18 2 views
0

Ich habe, was ich glaube, ein ziemlich einfaches Problem, aber konnte keine befriedigende Antwort finden. Kurz gesagt, ich möchte Verträge für untergeordnete Klassen eines Elternteils erzwingen, ohne Logik für jede untergeordnete Klasse hinzuzufügen. Code-Beispiel unten:Funktionen von Child-Klassen in Python zu dekorieren

class A(object): 

    @abc.abstractmethod 
    def do_thing(self, input): 
     raise NotImplementedError 

class A1(A): 

    def do_thing_decorator(self, do_thing_func): 
     def checked_do_thing(input): 
      check = do_thing_func(input) 
      if check != 1: 
       raise ValueError 
      return check 
     return checked_do_thing 

Die Frage ist also, wie kann ich Auto-schmückt die do_thing von Klassen implementiert Funktion von A1 vererben? Angenommen, es gibt auch eine A2 Klasse mit einer etwas anderen Prüfung.

Meine erste Untersuchung impliziert, dass Metaklassen der richtige Weg sind, aber es schwierig ist, gute Erklärungen dafür zu finden, wie sie funktionieren. Idealerweise sucht man nach etwas, das in Python 2.x funktioniert, aber wenn es nur 3.x-Lösungen gibt, bin ich froh, meine Codebasis zu ändern.

+1

Haben Sie sich die '__new __()' Methode angesehen? –

+0

@ IgnacioVazquez-Abrams keine unangemessene Lösung. Ich habe auf etwas auf der Methodenebene etwas intuitiver gehofft, werde aber wahrscheinlich darauf zurückgreifen, wenn ich nichts Besseres finde. –

Antwort

2

Zunächst einmal: Sie verwenden das abc Modul falsch (see the docs). Ihre Klasse A sollte abc.ABCMeta als Meta-Klasse haben. Wenn Sie also bereits eine Meta-Klasse verwenden, können Sie diese zu Ihrem Vorteil erweitern.

Eine Meta-Klasse, die von abc.ABCMeta erbt abstractmethod Arbeit zu machen und schmückt do_thing:

from abc import ABCMeta, abstractmethod 

class DecoratingMeta(ABCMeta): 
    def __new__(cls, *args): 
     new_class = super(DecoratingMeta, cls).__new__(cls, *args) 
     # decorating do_thing manually 
     new_class.do_thing = new_class.do_thing_decorator(new_class.do_thing) 
     return new_class 

Jetzt ist Ihre abstrakte Basisklasse mit einem Dekorateur Standardprüfung, die nichts tut:

# class Abstract(metaclass=ABCMeta): in python3 
class Abstract(object): 
    __metaclass__ = DecoratingMeta # remove this line in python3 
    @abstractmethod 
    def do_thing(self, input): 
     pass 

    @classmethod 
    def do_thing_decorator(cls, function): 
     return function  # default decorator that does nothing 

Beachten Sie, dass do_thing_decorator muss in diesem Fall eine Klassenmethode sein. Für Metaklassen, die in python3 und python2 funktionieren, siehe six.

Ihre Checker-Klasse, die eine bestimmte checker nur implementiert, sondern ist immer noch abstrakt:

class Checker(Abstract): 
    @classmethod 
    def do_thing_decorator(cls, function): 
     def do_checked_thing(self, input): 
      check = function(self, input) # NOT self.do_thing(input) else recursion error 
      if check != 1: 
       raise ValueError("Check failed") 
      return check 
     return do_checked_thing 

Beachten Sie, dass die Linie, die Sie check = do_thing_func(input) schrieb in einem Rekursionsfehlers führen würde.

Und Ihre konkrete Klasse mit einer Beispielimplementierung von do_thing:

class Concrete(Checker): 
    def do_thing(self, input): 
     return input # sample implementation 

Sie können überprüfen, ob do_thing(1) gelingt und do_thing(2) nicht

c = Concrete() 

c.do_thing(1) 
try: 
    c.do_thing(2) 
except ValueError: 
    print("check failed") 

Der Nachteil bei diesem Ansatz ist, dass Sie nicht machen können die do_thing_decorator Zusammenfassung.

So war das schon viel Text, aber wenn Sie überhaupt keine Meta-Klassen nicht verwenden wollen, ist es eine viel einfachere Art und Weise:

eine Klasse schreiben, die die Prüfung in der do_thing Methode führt durch mit zwei „abstrakt“ Methoden:

class Abstract(object): 
    def do_thing_func(self, input): 
     raise NotImplementedError() 

    def check_do_thing(self, result): 
     raise NotImplementedError() 

    # don't override this method 
    def do_thing(self, input): 
     result = self.do_thing_func(input) 
     self.check_do_thing(result) # may raise 
     return result # if it does not raise return the result 

Beachten Sie, dass do_thing_func und check_do_thing sind nicht wirklich abstrakt und dass man noch Instanciate Objekte vom Typ Abstract. Wenn Sie sie benötigen, um abstrakt zu sein, verwenden Sie eine Standard abc.ABCMeta Meta-Klasse hier.

nun eine Kontrolleur Klasse erstellen, die check_do_thing

class Checker(Abstract): 
    def check_do_thing(self, result): 
     if result != 1: 
      raise ValueError("check failed") 

Diese implementiert wird wesentlich einfacher, weil wir nicht hier einen Dekorateur brauchen.

Und schließlich die konkrete Klasse, die do_thing_func

class Concrete(Checker): 
    def do_thing_func(self, input): 
     return input # sample implementation 

Hinweis implementiert, dass Concrete jetzt do_thing_func implementieren muss, aber wenn Sie die Klasse verwenden Sie haben do_thing zu nennen.

Der Nachteil hier ist, dass Sie immer noch do_thing überschreiben und damit die Prüfung brechen können.