python:抽象基类'__init__():初始化还是验证? [关闭]

Posted

技术标签:

【中文标题】python:抽象基类\'__init__():初始化还是验证? [关闭]【英文标题】:python: abstract base class' __init__(): initializion or validation? [closed]python:抽象基类'__init__():初始化还是验证? [关闭] 【发布时间】:2011-07-05 05:34:04 【问题描述】:

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

ABC 的任何子类中都需要做一些工作,这很容易忘记或做错。我希望 ABC.__init__() 通过以下任一方式帮助发现此类错误:

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

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

这是一个用于说明目的的简化示例:

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

方法一:在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__()

哪个设计更好?

【问题讨论】:

【参考方案1】:

当然,人们更喜欢方法 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"

会很好用。另请注意,基类的初始化程序在其子类型中支持普通(无参数)初始化程序,以及可变长度参数初始化程序和关键字参数初始化程序。我建议始终使用这种形式,因为它不会在最简单(微不足道的构造函数)情况下强加语法,而是允许更复杂的功能而不强加维护。

【讨论】:

【参考方案2】:

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

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

编辑:关于防止实现类中的错误,我会通过单元测试来解决这个问题。我认为这比试图解释你的实现中的所有内容更全面:)

【讨论】:

【参考方案3】:

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

另外,它的代码更少:假设您在 ABC 中有 100 个这样的字段,而 ABC 有 100 个像 X 这样的子类...

【讨论】:

听起来很合理。所以我应该尽量不要依赖registry 作为一个列表,当我在子类中填充它时,对吗?也就是说,与其随意使用registry.append(new_item)registry[-1] = new_item,不如尝试使用self.add_to_registry(new_item),这将在ABC中定义为.append(),但如果需要,以后可以更改为.add() @max 我就是这样做的,是的。我不确定这是否是“pythonic”方式,我刚刚开始使用 python,我更多的是来自静态语言背景。但如果你不添加这些函数,为什么还要构建一个抽象超类呢? 好吧,我可能会使用 ABC 来验证东西。但我同意,看起来实际做一些工作更有用。

以上是关于python:抽象基类'__init__():初始化还是验证? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

Python面向对象编程——__init()__方法

流畅python学习笔记:第十一章:抽象基类

面对对象之继承 | Python

Mixin 类 __init__ 函数不会自动调用吗?

pytorch 参数初始化

Python继承-在子类中调用基类方法?