Эквивалент NotImplementedError для полей в Python - PullRequest
44 голосов
/ 20 июля 2009

В Python 2.x, если вы хотите пометить метод как абстрактный, вы можете определить его следующим образом:

class Base:
    def foo(self):
        raise NotImplementedError("Subclasses should implement this!")

Тогда, если вы забудете переопределить его, вы получите хорошее исключение для напоминания. Есть ли эквивалентный способ пометить поле как абстрактное? Или это все, что вы можете сделать в классе документации?

Сначала я подумал, что могу установить для поля значение NotImplemented, но когда я посмотрел, для чего оно на самом деле (богатые сравнения), это показалось оскорбительным.

Ответы [ 6 ]

45 голосов
/ 20 июля 2009

Да, вы можете. Используйте декоратор @property. Например, если у вас есть поле с именем «example», вы не можете сделать что-то вроде этого:

class Base(object):

    @property
    def example(self):
        raise NotImplementedError("Subclasses should implement this!")

Выполнение следующего приводит к NotImplementedError так, как вы хотите.

b = Base()
print b.example
31 голосов
/ 20 июля 2009

Альтернативный ответ:

@property
def NotImplementedField(self):
    raise NotImplementedError

class a(object):
    x = NotImplementedField

class b(a):
    # x = 5
    pass

b().x
a().x

Это как у Эвана, но сжато и дешево - вы получите только один экземпляр NotImplementedField.

3 голосов
/ 09 января 2017

Лучший способ сделать это - использовать Абстрактные базовые классы :

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, это не удастся тихо.

2 голосов
/ 20 июля 2009
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 принимается.

0 голосов
/ 24 августа 2018

Интересный способ справиться с этим - установить для атрибута значение 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
0 голосов
/ 30 декабря 2015

А вот мое решение:

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