Python中字段的NotImplementedError等效项

时间:2009-07-19 23:48:42

标签: python abstract-class

在Python 2.x中,当您想要将方法标记为抽象时,您可以像这样定义它:

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

然后,如果您忘记覆盖它,您会得到一个很好的提醒异常。是否有相同的方法将字段标记为抽象?或者你可以在类docstring中说明它吗?

起初我以为我可以将字段设置为NotImplemented,但是当我查看它的实际内容(丰富的比较)时,它似乎很侮辱。

8 个答案:

答案 0 :(得分:45)

是的,你可以。使用@property装饰器。例如,如果你有一个名为“example”的字段,那么你不能做这样的事情:

class Base(object):

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

运行以下内容会产生NotImplementedError,就像您想要的那样。

b = Base()
print b.example

答案 1 :(得分:31)

替代回答:

@property
def NotImplementedField(self):
    raise NotImplementedError

class a(object):
    x = NotImplementedField

class b(a):
    # x = 5
    pass

b().x
a().x

这就像Evan一样,但简洁又便宜 - 你只会获得一个NotImplementedField实例。

答案 2 :(得分:3)

更好的方法是使用Abstract Base Classes

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,这将无声地失败。

答案 3 :(得分:2)

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,因此如果多个继承的类使用此类,则它们并不都会验证派生类最多的字段。您可以使用元类自动执行此操作,但我没有深入研究。接受将字段定义为无。

答案 4 :(得分:1)

这个问题似乎对实例属性和类属性都开放,我将仅关注第一个主题。

因此,对于实例属性,对Evan's的另一种答案是使用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的作者。有关详细信息,请参见documentation

答案 5 :(得分:0)

这是我的解决方案:

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 :(得分:0)

一种有趣的处理方式是在父类中将属性设置为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 :(得分:0)

这是一个简单的示例,说明如何为Python 3中的子类别设置所需的属性/方法。

class Base:
    requires = ('foo', 'bar')

    def __init_subclass__(cls, **kwargs):
        for requirement in cls.requires:
            if not hasattr(cls, requirement):
                raise NotImplementedError(
                        f'"{cls.__name__}" must have "{requirement}".')
        super().__init_subclass__(**kwargs)