【问题标题】:How define constructor implementation for an Abstract Class in Python?如何在 Python 中为抽象类定义构造函数实现?
【发布时间】:2017-12-01 17:02:49
【问题描述】:

我正在尝试使用具有默认行为的构造函数声明一个抽象类A:所有子类都必须初始化一个成员self.n

from abc import ABCMeta

class A(object):
    __metaclass__ = ABCMeta

    def __init__(self, n):
        self.n = n

但是,我不想让A 类被实例化,因为它是一个抽象类。问题是,这实际上是允许的:

a = A(3)

这不会产生任何错误,而我希望它应该如此。

那么:如何在为构造函数定义默认行为的同时定义不可实例化的抽象类?

【问题讨论】:

标签: python python-2.7 oop abstract-class


【解决方案1】:

使__init__ 成为抽象方法:

from abc import ABCMeta, abstractmethod

class A(object):
    __metaclass__ = ABCMeta

    @abstractmethod
    def __init__(self, n):
        self.n = n


if __name__ == '__main__':
    a = A(3)

帮助:

TypeError: Can't instantiate abstract class A with abstract methods __init__

Python 3 版本:

from abc import ABCMeta, abstractmethod

class A(object, metaclass=ABCMeta):

    @abstractmethod
    def __init__(self, n):
        self.n = n


if __name__ == '__main__':
    a = A(3)

同样有效:

TypeError: Can't instantiate abstract class A with abstract methods __init__

【讨论】:

  • 这不会产生任何错误!实例化工作正常
  • 这仍然需要在每个基类中定义__init__(使用 super() 调用)。
  • 从类似的答案中引用我自己:问题:如果您需要将构造函数显式指定为抽象方法,那么将类定义为抽象有什么意义? (注意我并不是暗示所有抽象类都是,只是一个不可实例化的类)我希望这种行为是抽象类的默认行为
  • @dabadaba:将元类设置为 ABCmeta 不会将类声明为抽象类,它只会使之成为可能(通过将其中一个方法声明为抽象类)。
  • @MikeMüller 确实,但这并不一定意味着他们都应该每次都重新定义__init__。就像他们问的那样:“如何在为构造函数定义默认行为的同时定义不可实例化的抽象类?”。你的方法也是我想到的第一件事,但他们有不同的要求。但无论如何,这已被接受,所以我猜没关系。
【解决方案2】:

一个不太优雅的解决方案可能是这样的:

class A(object):
  def __init__(self, n):
    if self.__class__ == A:
      raise Exception('I am abstract!')
    self.n = n

用法

class B(A):
  pass
a = A(1)  # Will throw exception
b = B(1)  # Works fine as expected.

【讨论】:

  • 为什么不使用内置功能来创建一个带有抽象构造函数的实际抽象类(如@Mike Müller 所示)?
  • 因为 OP 根本没有要求抽象方法。她多次表示不想在子类中覆盖__init__
【解决方案3】:

您可以覆盖__new__ 方法以防止直接实例化。

class A(object):
    __metaclass__ = ABCMeta

    def __new__(cls, *args, **kwargs):
        if cls is A:
            raise TypeError(
                "TypeError: Can't instantiate abstract class {name} directly".format(name=cls.__name__)
            )
        return object.__new__(cls)

输出:

>>> A()
Traceback (most recent call last):
  File "<ipython-input-8-3cd318a12eea>", line 1, in <module>
    A()
  File "/Users/ashwini/py/so.py", line 11, in __new__
    "TypeError: Can't instantiate abstract class {name} directly".format(name=cls.__name__)
TypeError: TypeError: Can't instantiate abstract class A directly

【讨论】:

  • 您不应该将argskwargs 转发给object 构造函数。 object 不带参数。
  • @Rawing Where does it say so? 传递给__init__ 的参数稍后会传递给__new____call__
  • 那么试试A(3) 并告诉我它是否有效...在__new__ 返回一个实例后,传递给__new__ 的参数会自动转发给__init__。您不想将它们转发给object.__new__,因为它不接受它们。
  • 哦,没关系,这在 python 2 中有效。奇怪。不过,没有理由转发参数,是吗?
【解决方案4】:

您也应该使用 @abc.abstractmethod 装饰器将方法定义为抽象的。

【讨论】:

  • 问题:如果需要将构造函数显式指定为抽象方法,那么将类定义为抽象有什么意义? (请注意,我并不是暗示所有抽象类都只是一个不可实例化的类)
  • 我猜是因为 ABCMeta 不像其他语言那样完全是一个抽象类。
  • @dabadaba 我可以看到的一个好处是您可以在抽象类中定义默认方法,以及必须在其子类中实现的抽象方法。
【解决方案5】:

您可以实现如下所示:

from abc import ABC
class A(ABC):
    def __init__(self, n):
        self.n = n
        super(A,self).__init__()

实例化这个类会抛出错误

【讨论】:

    【解决方案6】:

    我不想让 A 类被实例化,因为它是一个抽象类。问题是,这实际上是允许的

    Python 本身不提供抽象类。 'abc' 模块,它提供了定义抽象基类的基础设施。

    抽象类是包含一个或多个抽象方法的类。在您的情况下,代码仍然是一个抽象类,应该提供“无法实例化抽象类”行为。

    如果您不想允许,程序需要更正: 即添加装饰器@abstractmethod。 以下代码在 python 3.6 中执行

    from abc import ABC, abstractmethod
    
    class Myabs(ABC):
    
        def __init__(self, connection):
            self.connection = connection
            print("1. calling from base init : {}".format(self.connection))
            super(Myabs, self).__init__()
        @abstractmethod
        def fun1(self, val):
            pass
    
    class D(Myabs):
        def fun1(self, val="hi"):
            print("calling from D: fun1")
    
    
    object0 = Myabs('connection') # Python 3.6.9
    

    输出:

    ClassConcept/abs_class.py Traceback(最近一次调用最后一次):文件 “ClassConcept/abs_class.py”,第 19 行,在 object0 = Myabs('connection') # Python 3.6.9 TypeError: Can't instantiate abstract class Myabs with abstract methods fun1

    进程以退出代码 1 结束

    【讨论】:

      猜你喜欢
      • 2021-06-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-18
      • 2016-05-05
      • 2018-09-26
      • 2021-04-28
      • 1970-01-01
      相关资源
      最近更新 更多