相当于 Python 中字段的 NotImplementedError

发布于 2024-07-26 23:19:06 字数 321 浏览 3 评论 0原文

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

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

然后,如果你忘记重写它,你会得到一个很好的提醒异常。 是否有等效的方法将字段标记为抽象? 或者你能做的就是在类文档字符串中声明它吗?

起初我以为我可以将该字段设置为 NotImplemented,但当我查找它的实际用途(丰富的比较)时,它似乎很滥用。

In Python 2.x when you want to mark a method as abstract, you can define it like so:

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

Then if you forget to override it, you get a nice reminder exception. Is there an equivalent way to mark a field as abstract? Or is stating it in the class docstring all you can do?

At first I thought I could set the field to NotImplemented, but when I looked up what it's actually for (rich comparisons) it seemed abusive.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

烟酉 2024-08-02 23:19:06

是的你可以。 使用@property装饰器。 例如,如果您有一个名为“example”的字段,那么您不能执行以下操作:

class Base(object):

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

运行以下命令会产生 NotImplementedError 就像您想要的那样。

b = Base()
print b.example

Yes, you can. Use the @property decorator. For instance, if you have a field called "example" then can't you do something like this:

class Base(object):

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

Running the following produces a NotImplementedError just like you want.

b = Base()
print b.example
撞了怀 2024-08-02 23:19:06

替代答案:

@property
def NotImplementedField(self):
    raise NotImplementedError

class a(object):
    x = NotImplementedField

class b(a):
    # x = 5
    pass

b().x
a().x

这就像 Evan 的,但简洁且便宜 - 您只会获得 NotImplementedField 的单个实例。

Alternate answer:

@property
def NotImplementedField(self):
    raise NotImplementedError

class a(object):
    x = NotImplementedField

class b(a):
    # x = 5
    pass

b().x
a().x

This is like Evan's, but concise and cheap--you'll only get a single instance of NotImplementedField.

別甾虛僞 2024-08-02 23:19:06

更好的方法是使用 抽象基类

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,这会默默地失败。

A better way to do this is using 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

Note that you should still have raise NotImplementedError instead of something like pass, because there is nothing preventing the inheriting class from calling super().demo_method(), and if the abstract demo_method is just pass, this will fail silently.

公布 2024-08-02 23:19:06
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 中,因此如果多个继承类使用此类型,它们不会全部验证最派生类的字段。 您也许可以使用元类自动执行此操作,但我没有深入研究这一点。 接受将字段定义为“无”。

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()

Note the passing of the class type into require_abstract_fields, so if multiple inherited classes use this, they don't all validate the most-derived-class's fields. You might be able to automate this with a metaclass, but I didn't dig into that. Defining a field to None is accepted.

幸福不弃 2024-08-02 23:19:06

看来这个问题对实例属性和类属性都是开放的,我将只关注第一个主题。

因此,对于属性来说,Evan 的替代答案是使用 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 的作者。 有关详细信息,请参阅文档

It seem that this question was open to both instance attributes and class attributes, I'll focus on the first topic only.

So, for instance attributes, an alternate answer to Evan's is to define a mandatory field using pyfields:

from pyfields import field

class Base(object):
    example = field(doc="This should contain an example.")

b = Base()
b.example

yields

pyfields.core.MandatoryFieldInitError: 
   Mandatory field 'example' has not been initialized yet 
   on instance <__main__.Base object at 0x000002C1000C0C18>.

Granted, it does not provide you with the ability to edit the error message by talking about subclasses. But in a way it is more realistic to not talk about subclasses - indeed in python, attributes can be overridden on instances of the base class - not only in subclasses.

Note: I'm the author of pyfields. See documentation for details.

宁愿没拥抱 2024-08-02 23:19:06

下面是一个如何在 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)

Here is a simple example how to set required properties/methods for sublasses in 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)
萌梦深 2024-08-02 23:19:06

这是我的解决方案:

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

And here is my 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)

It can be used as

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
剪不断理还乱 2024-08-02 23:19:06

处理此问题的一个有趣模式是在父类中将属性设置为 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

An interesting pattern to handle this is to set attribute to None in the parent class and to access the attribute with a function that ensure it has been set in the child class.

Here is an example from 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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文