python:抽象基类'__init __():初始化还是验证?

时间:2011-02-27 12:43:42

标签: python oop abstract-class software-design

class ABC是一个“抽象基类”。 class X是它的子类。

有些工作需要在ABC的任何子类中完成,这很容易被遗忘或做错。我希望ABC.__init__()能够通过以下两种方式来解决这些错误:

(1)开始这项工作,或 (2)验证它

这会影响在super().__init__()的开头或结尾调用X.__init__()

以下是用于说明目的的简化示例:

假设ABC的每个子类都必须具有属性registry,并且它必须是列表。 ABC.__init__()可以(1)初始化registry或(2)检查它是否已正确创建。以下是每种方法的示例代码。

方法1:在ABC中初始化

class ABC:
    def __init__(self):
        self.registry = []

class X:
    def __init__(self):
        super().__init__()
        # populate self.registry here
        ...

方法2:在ABC中验证

class ABC:
    class InitializationFailure(Exception):
        pass
    def __init__(self):
        try:
            if not isinstance(self.registry, list):
                raise ABC.InitializationError()
        except AttributeError:
            raise ABC.InitializationError()

class X:
    def __init__(self):
        self.registry = []
        # populate self.registry here
        ...
        super().__init__()

哪个是更好的设计?

3 个答案:

答案 0 :(得分:14)

当然,人们更喜欢方法1接近2(因为方法2将基础降级为标记接口而不是实现抽象功能)。但是,方法1本身并不能满足您的目标,即防止子类型开发人员忘记正确实现super()调用,确保初始化。

您可能希望查看“工厂”模式以减轻子类型实现者忘记初始化的可能性。考虑:

class AbstractClass(object):
    '''Abstract base class template, implementing factory pattern through 
       use of the __new__() initializer. Factory method supports trivial, 
       argumented, & keyword argument constructors of arbitrary length.'''

   __slots__ = ["baseProperty"]
   '''Slots define [template] abstract class attributes. No instance
       __dict__ will be present unless subclasses create it through 
       implicit attribute definition in __init__() '''

   def __new__(cls, *args, **kwargs):
       '''Factory method for base/subtype creation. Simply creates an
       (new-style class) object instance and sets a base property. '''
       instance = object.__new__(cls)

       instance.baseProperty = "Thingee"
       return instance

这个基类可以比方法1更简单地扩展,只使用三(3)行代码san-commment,如下所示:

class Sub(AbstractClass):
   '''Subtype template implements AbstractClass base type and adds
      its own 'foo' attribute. Note (though poor style, that __slots__
      and __dict__ style attributes may be mixed.'''

   def __init__(self):
       '''Subtype initializer. Sets 'foo' attribute. '''
       self.foo = "bar"

请注意,虽然我们没有调用超类的构造函数,但是baseProperty将被初始化:

Python 2.6.1 (r261:67515, Jun 24 2010, 21:47:49) 
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from TestFactory import *
>>> s = Sub()
>>> s.foo
'bar'
>>> s.baseProperty
'Thingee'
>>> 

正如其评论所示,基类AbstractClass不需要使用 slots ,它可以通过在 new ()初始化程序中设置属性来轻松“隐式”定义属性。例如:

instance.otherBaseProperty = "Thingee2"

会正常工作。另请注意,基类的初始化程序在其子类型中支持普通(无arg)初始化程序,以及可变长度的arugmented和关键字参数初始值设定项。我建议始终使用此表单,因为它不会在最简单(简单的构造函数)情况下强加语法,但允许更复杂的功能而不强加维护。

答案 1 :(得分:2)

在您提供的示例中,我将按照您的方法1进行操作。但是,我会将类ABC主要视为X和其他实现某个接口的类的实现帮助程序。所述接口由属性'registry'组成。

至少逻辑上,您应该区分X和其他类共享的接口,以及帮助您实现它的基类。也就是说,单独定义有一个接口(例如“ABC”),它公开一个列表“注册表”。然后,您可以决定将接口的实现分解为接口ABC的实现者的公共基类(概念上是混合),因为它使得引入新的实现类(除了X)非常容易。

编辑:关于防止实施课程中的错误,我将通过单元测试来解决这个问题。我认为这比考虑实施中的所有内容更全面:)

答案 2 :(得分:1)

第一个是更好的设计,因为子类不需要知道你已经用列表实现了注册表。例如,您可以提供一个_is_in_registry函数,该函数接受一个参数,并返回该元素是否在注册表中。然后,您可以稍后更改超类并使用集替换列表,因为元素只能在注册表中出现一次,并且您不需要更改子类。

此外,代码更少:想象一下,ABC中有100个这样的字段,ABC有100个子类,如X ...