En Python 2.x, lorsque vous souhaitez marquer une méthode comme abstraite, vous pouvez la définir comme suit:
class Base:
def foo(self):
raise NotImplementedError("Subclasses should implement this!")
Ensuite, si vous oubliez de le remplacer, vous obtenez une exception de rappel Nice. Existe-t-il un moyen équivalent de marquer un champ comme étant abstrait? Ou le fait-il dans la classe docstring tout ce que vous pouvez faire?
Au début, je pensais pouvoir définir le champ sur NotImplemented, mais lorsque j’ai cherché à quoi il sert (comparaisons riches), cela a semblé abusif.
Oui, vous pouvez. Utilisez le décorateur @property
. Par exemple, si vous avez un champ appelé "exemple", vous ne pouvez pas faire quelque chose comme ceci:
class Base(object):
@property
def example(self):
raise NotImplementedError("Subclasses should implement this!")
L'exécution de ce qui suit produit une NotImplementedError
comme vous le souhaitez.
b = Base()
print b.example
Réponse alternative:
@property
def NotImplementedField(self):
raise NotImplementedError
class a(object):
x = NotImplementedField
class b(a):
# x = 5
pass
b().x
a().x
Cela ressemble à celui d'Evan, mais concis et bon marché - vous ne recevrez qu'une seule instance de NotImplementedField.
Une meilleure façon de faire est d'utiliser Classes de base abstraites :
import abc
class Foo(abc.ABC):
@property
@abc.abstractmethod
def demo_attribute(self):
raise NotImplementedError
@abc.abstractmethod
def demo_method(self):
raise NotImplementedError
class BadBar(Foo):
pass
class GoodBar(Foo):
demo_attribute = 'yes'
def demo_method(self):
return self.demo_attribute
bad_bar = BadBar()
# TypeError: Can't instantiate abstract class BadBar \
# with abstract methods demo_attribute, demo_method
good_bar = GoodBar()
# OK
Notez que vous devriez toujours avoir raise NotImplementedError
au lieu de quelque chose comme pass
, car rien n'empêche la classe héritante d'appeler super().demo_method()
, et si abstract demo_method
est simplement pass
, cela échouera en silence.
def require_abstract_fields(obj, cls):
abstract_fields = getattr(cls, "abstract_fields", None)
if abstract_fields is None:
return
for field in abstract_fields:
if not hasattr(obj, field):
raise RuntimeError, "object %s failed to define %s" % (obj, field)
class a(object):
abstract_fields = ("x", )
def __init__(self):
require_abstract_fields(self, a)
class b(a):
abstract_fields = ("y", )
x = 5
def __init__(self):
require_abstract_fields(self, b)
super(b, self).__init__()
b()
a()
Notez le passage du type de classe dans require_abstract_fields
; ainsi, si plusieurs classes héritées l'utilisent, elles ne valident pas toutes les champs de la classe la plus dérivée. Vous pourrez peut-être automatiser cela avec une métaclasse, mais je n'ai pas creusé dans cela. Définir un champ sur Aucun est accepté.
Et voici ma solution:
def not_implemented_method(func):
from functools import wraps
from inspect import getargspec, formatargspec
@wraps(func)
def wrapper(self, *args, **kwargs):
c = self.__class__.__name__
m = func.__name__
a = formatargspec(*getargspec(func))
raise NotImplementedError('\'%s\' object does not implement the method \'%s%s\'' % (c, m, a))
return wrapper
def not_implemented_property(func):
from functools import wraps
from inspect import getargspec, formatargspec
@wraps(func)
def wrapper(self, *args, **kwargs):
c = self.__class__.__name__
m = func.__name__
raise NotImplementedError('\'%s\' object does not implement the property \'%s\'' % (c, m))
return property(wrapper, wrapper, wrapper)
Il peut être utilisé comme
class AbstractBase(object):
@not_implemented_method
def test(self):
pass
@not_implemented_property
def value(self):
pass
class Implementation(AbstractBase):
value = None
def __init__(self):
self.value = 42
def test(self):
return True
Un modèle intéressant pour gérer cela consiste à définir l'attribut sur None
dans la classe parente et à accéder à l'attribut avec une fonction garantissant qu'il a été défini dans la classe enfant.
Voici un exemple de Django-rest-framework :
class GenericAPIView(views.APIView):
[...]
serializer_class = None
[...]
def get_serializer_class(self):
assert self.serializer_class is not None, (
"'%s' should either include a `serializer_class` attribute, "
"or override the `get_serializer_class()` method."
% self.__class__.__name__
)
return self.serializer_class