【问题标题】:Equivalent of NotImplementedError for fields in Python等效于 Python 中的字段的 NotImplementedError
【发布时间】:2010-11-12 04:19:49
【问题描述】:

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

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

然后,如果您忘记覆盖它,您会得到一个很好的提醒异常。是否有等效的方法将字段标记为抽象?还是在类文档字符串中说明所有你能做的?

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

【问题讨论】:

  • 它仍然有效,即使它最初的意图是进行丰富的比较。它有什么问题?
  • 第一个问题是您可以从对象 (myvar = Base.field) 中读取字段,接下来您知道整个地方都有 NotImplementeds,直到其他部分尝试使用它并获得神秘的 AttributeError。
  • 第二个问题是 IMO 它妨碍了可读性(“那个丰富的比较在那里做了什么?我错过了什么吗?)Evan 的解决方案以一种熟悉的方式准确地表达了正在发生的事情。
  • @Kiv:请不要评论您的问题。请根据您提出的具体观点更新您的问题。

标签: python abstract-class


【解决方案1】:

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

class Base(object):

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

运行以下命令会产生您想要的 NotImplementedError

b = Base()
print b.example

【讨论】:

  • 这更简单,但我喜欢我的版本如何立即抛出,而不仅仅是在碰巧使用该属性时。
  • 但是格伦,如果example 属性设置在其他时间点会怎样?如果它立即抛出它,那么它可能永远没有机会通过其他方式设置。请记住,字段和方法可以随时设置为类,而不仅仅是在定义类时。
  • 啊,这就是我想要的。
  • 如果我正在定义一个基类,它希望用户定义一个方法(或字段),我希望它在基类处于活动状态时始终被定义,来自 初始化。我会考虑稍后将其定义为错误,因为我可能想从 init 访问它们。当然,您可以使用不同的规则定义您的类,但这似乎是最清楚的。 (当然,由于我最初是一名 C++ 程序员,所以我喜欢漂亮、严格、定义良好的接口规则。)
  • @Glenn,两个 cmets :这听起来像 C++ 哲学,而 Python 哲学要宽松得多 - 您只关心尝试访问属性时得到的结果。当您不尝试访问它时,它可以是任何东西,包括未定义的。 (当然这不是必需的,这只是很多 Python 代码的编写方式,我认为这是语言创建者/维护者的意图。)
【解决方案2】:

替代答案:

@property
def NotImplementedField(self):
    raise NotImplementedError

class a(object):
    x = NotImplementedField

class b(a):
    # x = 5
    pass

b().x
a().x

这与 Evan 的类似,但简洁且便宜——您只会获得一个 NotImplementedField 实例。

【讨论】:

  • 聪明的格伦。 :) 我能看到的唯一缺点是,当 NotImplementedError 被抛出时,您无法指定要显示的不同消息。
  • 您可以将 NotImplementedField 定义为一个接收要显示的消息的函数。当没有附加消息时,您必须变得有点聪明才能使用函数的单个实例来保持它——缓存一个没有消息的单例——但就是这样。
  • @umbrae:见我之前的评论(两年前);如果您愿意,这很容易实现。
【解决方案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,这将失败默默地。

【讨论】:

  • raise NotImplementedErrorraise NotImplementedError(something) 有什么区别。从表面上看,前者提出了一个类,而后者提出了一个实例。这种模棱两可是一个问题吗?
  • @Reb.Cabin 两者都有效。通常,当您不提供任何参数时,您会提出NotImplementedError,如果您提供参数,您会提出raise NotImplementedError("something")。请参阅:stackoverflow.com/a/16709222/2063031
  • 我很好奇 - 为什么使用抽象基类更好?有什么额外的好处吗?否则,这表面上会出现一些额外的行,并且需要导入额外的类才能达到相同的结果。
  • @YPCrumble 我认为主要的好处是当您尝试实例化一个不覆盖所有抽象方法和属性的类时会出现错误,而不是在运行时出现异常NotImplemented 方法被调用。在实践中,这是否会产生影响可能取决于您的具体应用。
【解决方案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】:

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

    因此,例如属性,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

    【讨论】:

      【解决方案6】:

      这是一个简单的示例,如何在 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)
      

      【讨论】:

        【解决方案7】:

        这是我的解决方案:

        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
        

        【讨论】:

          【解决方案8】:

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-07-10
            • 2017-02-24
            • 1970-01-01
            • 1970-01-01
            • 2011-03-07
            • 2015-08-04
            相关资源
            最近更新 更多