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?
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!!!!!!!
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
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?
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.
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.
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.
Improvising on @mkorpela excellente réponse , voici une version avec
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.
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()