Эквивалент NotImplementedError для полей в Python
В Python 2.x, когда вы хотите пометить метод как абстрактный, вы можете определить его так:
class Base:
def foo(self):
raise NotImplementedError("Subclasses should implement this!")
Затем, если вы забудете переопределить его, вы получите замечательное напоминание. Существует ли эквивалентный способ отметить поле как абстрактное? Или вы указываете, что это в классе docstring, что вы можете сделать?
Сначала я думал, что могу установить поле NotImplemented, но когда я посмотрел, на что он на самом деле (богатые сравнения), он казался оскорбительным.
Ответы
Ответ 1
Да, вы можете. Используйте декоратор @property
. Например, если у вас есть поле под названием "пример", вы не можете сделать что-то вроде этого:
class Base(object):
@property
def example(self):
raise NotImplementedError("Subclasses should implement this!")
Запустив следующую команду, выполните NotImplementedError
так, как вы хотите.
b = Base()
print b.example
Ответ 2
Альтернативный ответ:
@property
def NotImplementedField(self):
raise NotImplementedError
class a(object):
x = NotImplementedField
class b(a):
# x = 5
pass
b().x
a().x
Это похоже на Evan's, но кратким и дешевым - вы получите только один экземпляр NotImplementedField.
Ответ 3
Лучший способ сделать это - использовать Абстрактные базовые классы:
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
Обратите внимание, что вы все равно должны иметь raise NotImplementedError
вместо pass
, потому что ничего не мешает наследующему классу вызвать super().demo_method()
, а если абстрактный demo_method
равен pass
, это не удастся тихо.
Ответ 4
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()
Обратите внимание на прохождение типа класса в require_abstract_fields
, поэтому, если это используется несколькими унаследованными классами, они не все проверяют поля самого производного класса. Возможно, вы сможете автоматизировать это с помощью метакласса, но я не понял этого. Определение поля в None принято.
Ответ 5
И вот мое решение:
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)
Его можно использовать как
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
Ответ 6
Интересный способ справиться с этим - установить для атрибута значение None
в родительском классе и получить доступ к атрибуту с помощью функции, обеспечивающей его установку в дочернем классе.
Вот пример из 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
Ответ 7
Кажется, что этот вопрос был открыт как для атрибутов экземпляра, так и для атрибутов класса, я сосредоточусь только на первой теме.
Так, например, для атрибутов, альтернативный ответ Эвану - определить обязательное поле, используя pyfields
:
from pyfields import field
class Base(object):
example = field(doc="This should contain an example.")
b = Base()
b.example
дает
pyfields.core.MandatoryFieldInitError:
Mandatory field 'example' has not been initialized yet
on instance <__main__.Base object at 0x000002C1000C0C18>.
Конечно, он не дает вам возможности редактировать сообщение об ошибке, говоря о подклассах. Но в некотором смысле более реалистично не говорить о подклассах - действительно, в python атрибуты могут быть переопределены в экземплярах базового класса - не только в подклассах.
Примечание: я автор pyfields
. Подробности смотрите в документации.