web-dev-qa-db-fra.com

En Python, comment indiquer que je remplace une méthode?

En Java, par exemple, l'annotation @Override permet non seulement de vérifier le remplacement au moment de la compilation, mais constitue également un excellent code auto-documenté. 

Je suis juste à la recherche de documentation (mais si c'est un indicateur pour un vérificateur comme pylint, c'est un bonus). Je peux ajouter un commentaire ou une docstring quelque part, mais quel est le moyen idiomatique d'indiquer un remplacement en Python?

125
Bluu

UPDATE (23.05.2015): Sur la base de cette réponse et de la réponse de fwc: s, j'ai créé un paquet installable par pip https://github.com/mkorpela/overrides

De temps en temps, je finis ici par regarder cette question . Cela se produit principalement après avoir (à nouveau) vu le même bogue dans notre base de code: quelqu'un a oublié une "interface" qui implémente une classe en renommant une méthode dans "l'interface". ..

Eh bien, Python n’est pas Java, mais Python a le pouvoir - et explicite, c’est mieux qu’implicite - et il existe de véritables cas concrets dans le monde réel où cela m’aurait aidé.

Voici donc un croquis du décorateur de remplacement. Cela vérifiera que la classe donnée en tant que paramètre a le même nom de méthode (ou quelque chose) que la méthode en cours de décoration.

Si vous pouvez penser à une meilleure solution, postez-la ici!

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

Cela fonctionne comme suit:

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


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

et si vous faites une version défectueuse, une erreur d'assertion sera générée lors du chargement de la classe:

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

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

Voici une implémentation qui ne nécessite pas la spécification du nom interface_class.

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

Si vous souhaitez utiliser ceci uniquement à des fins de documentation, vous pouvez définir votre propre décorateur de remplacement:

def override(f):
    return f


class MyClass (BaseClass):

    @override
    def method(self):
        pass

Ce n’est vraiment rien d’utile, à moins que vous ne créiez un remplacement (f) de telle sorte qu’il vérifie le remplacement.

Mais alors, c'est Python, pourquoi l'écrire comme si c'était Java?

10
Ber

Python n'est pas Java. Bien entendu, il n’existe vraiment pas de vérification au moment de la compilation. 

Je pense qu'un commentaire dans le docstring suffit. Cela permet à tout utilisateur de votre méthode de taper help(obj.method) et de voir que la méthode est un remplacement. 

Vous pouvez également explicitement étendre une interface avec class Foo(Interface), ce qui permettra aux utilisateurs de taper help(Interface.method) pour avoir une idée de la fonctionnalité que votre méthode est censée fournir.

7
Triptych

Comme d'autres l'ont dit, contrairement à Java, il n'y a pas de balise @Overide, mais vous pouvez créer votre propre décor à l'aide de décorateurs. Toutefois, je vous suggérerais d'utiliser la méthode globale getattrib () au lieu d'utiliser le dict interne pour obtenir le résultat suivant:

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

Si vous le vouliez, vous pourriez attraper getattr () de votre propre façon, mais votre méthode getattr est meilleure dans ce cas.

De plus, cela intercepte tous les éléments liés à une classe, y compris les méthodes de classe et les vairables.

2
Bicker x 2

Sur la base de l'excellente réponse de @ mkorpela, j'ai écrit un package similaire ( ipromise pypigithub ) qui effectue de nombreux autres contrôles:

Supposons que A hérite de B et C. Et B hérite de C. vérifie immédiatement que

  • Si A.f remplace B.f, B.f doit exister et A doit hériter de B. (Il s'agit de la vérification du package de substitutions).

  • Vous n'avez pas le modèle A.f qui déclare qu'il remplace B.f, qui déclare ensuite qu'il remplace Cf. A devrait indiquer qu'il remplace Cf car B pourrait décider de ne pas redéfinir cette méthode, ce qui ne devrait pas entraîner de mises à jour en aval.

  • Vous n'avez pas le motif A.f qui déclare qu'il remplace Cf, mais B.f ne déclare pas son remplacement.

  • Vous n'avez pas le modèle A.f qui déclare qu'il remplace celui-ci, mais B.f qui déclare qu'il remplace certains D.f.

Il présente également diverses fonctionnalités permettant de marquer et de vérifier la mise en œuvre d'une méthode abstraite.

1
Neil G

Improvising on @mkorpela excellente réponse , voici une version avec 

contrôles plus précis, nommage et objets d'erreur soulevés

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


Voici à quoi cela ressemble dans la pratique:

NotImplementedError "non implémenté dans la classe de base"

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

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

entraîne une erreur NotImplementedError plus descriptive

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

un paquet entier

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 "type implémenté attendu"

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

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

entraîne une erreur NotImplementedError plus descriptive

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

un paquet entier

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'>




La réponse intéressante de @mkorpela est que le contrôle a lieu pendant une phase d’initialisation. Le contrôle n'a pas besoin d'être "exécuté". En référence aux exemples précédents, class B n'est jamais initialisé (B()), mais la NotImplementedError sera toujours levée. Cela signifie que les erreurs overrides sont détectées plus tôt.

0
JamesThomasMoon1979

Hear est le plus simple et fonctionne sous Jython avec les classes Java:

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