wake-up-neo.com

Wie kann ich in Python angeben, dass ich eine Methode überschreibe?

In Java zum Beispiel ermöglicht die Annotation @Override nicht nur die Überprüfung der Außerkraftsetzung während der Kompilierung, sondern auch hervorragenden selbstdokumentierenden Code. 

Ich suche nur nach Dokumentation (obwohl dies ein Indikator für einen Checker wie Pylint ist, ist das ein Bonus). Ich kann irgendwo einen Kommentar oder einen Dokumentstring hinzufügen, aber wie kann ich auf eine Überschreibung in Python hinweisen?

125
Bluu

UPDATE (23.05.2015): Basierend auf dieser und der Antwort von fwc: Ich habe ein installierbares Pip-Paket erstellt https://github.com/mkorpela/overrides

Von Zeit zu Zeit ende ich damit, diese Frage zu betrachten ... Dies geschieht hauptsächlich nachdem (wieder) der gleiche Fehler in unserer Codebasis gesehen wurde: Jemand hat die Implementierung einer "interface" -Klasse beim Umbenennen einer Methode in der "interface" vergessen. ..

Nun, Python ist kein Java, aber Python hat Macht - und explizit ist besser als implizit - und es gibt wirklich konkrete Fälle in der realen Welt, in denen mir diese Sache geholfen hätte.

Hier ist also eine Skizze der Überschreibungen des Dekorators. Dadurch wird geprüft, ob die als Parameter angegebene Klasse denselben Methodennamen (oder einen anderen Namen) hat wie die zu dekorierende Methode.

Wenn Sie sich eine bessere Lösung vorstellen können, schreiben Sie es hier!

def overrides(interface_class):
    def overrider(method):
        assert(method.__in dir(interface_class))
        return method
    return overrider

Es funktioniert wie folgt:

class MySuperInterface(object):
    def my_method(self):
        print 'hello world!'


class ConcreteImplementer(MySuperInterface):
    @overrides(MySuperInterface)
    def my_method(self):
        print 'hello kitty!'

und wenn Sie eine fehlerhafte Version ausführen, wird während des Klassenladens ein Assertionsfehler ausgelöst:

class ConcreteFaultyImplementer(MySuperInterface):
    @overrides(MySuperInterface)
    def your_method(self):
        print 'bye bye!'

>> AssertionError!!!!!!!
170
mkorpela

Hier ist eine Implementierung, die keine Angabe des Namens der interface_class erfordert.

import inspect
import re

def overrides(method):
    # actually can't do this because a method is really just a function while inside a class def'n  
    #assert(inspect.ismethod(method))

    stack = inspect.stack()
    base_classes = re.search(r'class.+\((.+)\)\s*\:', stack[2][4][0]).group(1)

    # handle multiple inheritance
    base_classes = [s.strip() for s in base_classes.split(',')]
    if not base_classes:
        raise ValueError('overrides decorator: unable to determine base class') 

    # stack[0]=overrides, stack[1]=inside class def'n, stack[2]=outside class def'n
    derived_class_locals = stack[2][0].f_locals

    # replace each class name in base_classes with the actual class type
    for i, base_class in enumerate(base_classes):

        if '.' not in base_class:
            base_classes[i] = derived_class_locals[base_class]

        else:
            components = base_class.split('.')

            # obj is either a module or a class
            obj = derived_class_locals[components[0]]

            for c in components[1:]:
                assert(inspect.ismodule(obj) or inspect.isclass(obj))
                obj = getattr(obj, c)

            base_classes[i] = obj


    assert( any( hasattr(cls, method.__name__) for cls in base_classes ) )
    return method
25
fwc

Wenn Sie dies nur zu Dokumentationszwecken wünschen, können Sie einen eigenen Überschreibungsdekorator definieren:

def override(f):
    return f


class MyClass (BaseClass):

    @override
    def method(self):
        pass

Dies ist wirklich nichts anderes als eine Augenweide, es sei denn, Sie erstellen eine Überschreibung (f) auf eine Weise, die tatsächlich nach einer Überschreibung sucht.

Aber das ist Python, warum schreibt man es so, als wäre es Java?

10
Ber

Python ist kein Java. Es gibt natürlich keine Überprüfung der Kompilierzeit. 

Ich denke, ein Kommentar im Dokument ist reichlich. Auf diese Weise kann jeder Benutzer Ihrer Methode help(obj.method) eingeben und feststellen, dass es sich bei der Methode um eine Überschreibung handelt. 

Sie können eine Schnittstelle auch explizit mit class Foo(Interface) erweitern, sodass Benutzer help(Interface.method) eingeben können, um eine Vorstellung von der Funktionalität zu erhalten, die von Ihrer Methode bereitgestellt werden soll.

7
Triptych

Wie andere schon sagten, im Gegensatz zu Java gibt es kein @Overide-Tag. Oben können Sie jedoch Ihr eigenes Dekorator verwenden. Ich würde jedoch vorschlagen, die globale Methode getattrib () anstelle des internen Diktats zu verwenden, sodass Sie etwa Folgendes erhalten:

def Override(superClass):
    def method(func)
        getattr(superClass,method.__name__)
    return method

Wenn Sie wollten, könnten Sie getattr () in Ihrem eigenen Fall fangen. Try catch hebt Ihren eigenen Fehler an, aber ich denke, die getattr-Methode ist in diesem Fall besser.

Dies erfasst auch alle Elemente, die an eine Klasse gebunden sind, einschließlich Klassenmethoden und vairables

2
Bicker x 2

Basierend auf der großartigen Antwort von @ mkorpela habe ich ein ähnliches Paket geschrieben ( ipromise pypigithub ), das viele weitere Prüfungen durchführt:

Angenommen, A erbt von B und C. Und B erbt von C. ipromise prüft dies

  • Wenn A.f B.f überschreibt, muss B.f vorhanden sein, und A muss von B. erben (dies ist die Prüfung aus dem Überschreibungspaket).

  • Sie haben nicht das Muster A.f, das angibt, dass es B.f überschreibt, und das dann angibt, dass es C.f überschreibt. A sollte sagen, dass es C.f überschreibt, da B möglicherweise beschließt, diese Methode nicht mehr zu überschreiben, und dies sollte keine nachgelagerten Aktualisierungen zur Folge haben.

  • Sie haben nicht das Muster A.f deklariert, dass es C.f überschreibt, aber B.f deklariert seine Überschreibung nicht.

  • Sie haben nicht das Muster A.f deklariert, dass es C.f überschreibt, aber B.f deklariert, dass es von einigen D.f.

Es verfügt auch über verschiedene Funktionen zum Markieren und Überprüfen der Implementierung einer abstrakten Methode.

1
Neil G

Improvisieren auf @mkorpela tolle Antwort , hier ist eine Version mit 

genauere Überprüfungen, Benennung und Ausgabe von Fehlerobjekten

def overrides(interface_class):
    """
    Function override annotation.
    Corollary to @abc.abstractmethod where the override is not of an
    abstractmethod.
    Modified from answer https://stackoverflow.com/a/8313042/471376
    """
    def confirm_override(method):
        if method.__not in dir(interface_class):
            raise NotImplementedError('function "%s" is an @override but that'
                                      ' function is not implemented in base'
                                      ' class %s'
                                      % (method.__name__,
                                         interface_class)
                                      )

        def func():
            pass

        attr = getattr(interface_class, method.__name__)
        if type(attr) is not type(func):
            raise NotImplementedError('function "%s" is an @override'
                                      ' but that is implemented as type %s'
                                      ' in base class %s, expected implemented'
                                      ' type %s'
                                      % (method.__name__,
                                         type(attr),
                                         interface_class,
                                         type(func))
                                      )
        return method
    return confirm_override


So sieht es in der Praxis aus:

NotImplementedError "nicht in Basisklasse implementiert"

class A(object):
    # ERROR: `a` is not a implemented!
    pass

class B(A):
    @overrides(A)
    def a(self):
        pass

führt zu einem beschreibenden NotImplementedError-Fehler

function "a" is an @override but that function is not implemented in base class <class '__main__.A'>

voller Stapel

Traceback (most recent call last):
  …
  File "C:/Users/user1/project.py", line 135, in <module>
    class B(A):
  File "C:/Users/user1/project.py", line 136, in B
    @overrides(A)
  File "C:/Users/user1/project.py", line 110, in confirm_override
    interface_class)
NotImplementedError: function "a" is an @override but that function is not implemented in base class <class '__main__.A'>


NotImplementedError "erwarteter implementierter Typ"

class A(object):
    # ERROR: `a` is not a function!
    a = ''

class B(A):
    @overrides(A)
    def a(self):
        pass

führt zu einem beschreibenden NotImplementedError-Fehler

function "a" is an @override but that is implemented as type <class 'str'> in base class <class '__main__.A'>, expected implemented type <class 'function'>

voller Stapel

Traceback (most recent call last):
  …
  File "C:/Users/user1/project.py", line 135, in <module>
    class B(A):
  File "C:/Users/user1/project.py", line 136, in B
    @overrides(A)
  File "C:/Users/user1/project.py", line 125, in confirm_override
    type(func))
NotImplementedError: function "a" is an @override but that is implemented as type <class 'str'> in base class <class '__main__.A'>, expected implemented type <class 'function'>




Das Tolle an der @mkorpela-Antwort ist, dass die Überprüfung während einer Initialisierungsphase erfolgt. Die Prüfung muss nicht "ausgeführt" werden. In Bezug auf die vorherigen Beispiele wird class B nie initialisiert (B()), die NotImplementedError wird jedoch trotzdem angehoben. Dies bedeutet, dass overrides-Fehler früher erkannt werden.

0

Hear ist am einfachsten und arbeitet unter Jython mit Java-Klassen:

class MyClass(SomeJavaClass):
     def __init__(self):
         setattr(self, "name_of_method_to_override", __method_override__)

     def __method_override__(self, some_args):
         some_thing_to_do()
0
user3034016