制作具体的类摘要,保留构造函数

时间:2014-10-20 09:16:55

标签: python oop inheritance abstract-class

假设您有一个具体的类

class Knight(object):
    def __init__(self, name):
        self._name = name
    def __str__(self):
        return "Sir {} of Camelot".format(self.name)

现在碰巧类层次结构必须改变。 Knight应该成为一个抽象的基类,为各种城堡的骑士提供一堆具体的子类。很容易:

class Knight(metaclass=ABCMeta):  # Python 3 syntax
    def __str__(self):
        return "Sir {} of {}".format(self.name, self.castle)
    @abstractmethod
    def sing():
        pass

class KnightOfTheRoundTable(Knight):
    def __init__(self, name):
        self.name = name
    @property
    def castle(self):
        return "Camelot"
    @staticmethod
    def sing():
        return ["We're knights of the round table",
                "We dance whenever we're able"]

但现在所有使用Knight("Galahad")构建Knight的代码都被破坏了。我们可以保持Knight不变并引入BaseKnight,但是检查isinstance(x, Knight)并且应该对任何骑士工作的代码可能必须更改以检查BaseKnight而不是。

如何将具体类转换为抽象类,同时保留构造函数和isinstance检查?

2 个答案:

答案 0 :(得分:2)

使现有类成为基类,但overload __new__在尝试实例化基类时返回子类:

class Knight(metaclass=ABCMeta):
    def __new__(cls, *args, **kwargs):
        if cls is Knight:
            # attempt to construct base class, defer to default subclass
            return KnightOfTheRoundTable(*args, **kwargs)
        else:
            obj = super(Knight, cls).__new__(cls)
            obj.__init__(*args, **kwargs)
            return obj
    def __str__(self):
        return "Sir {} of {}".format(self.name, self.castle)
    @abstractmethod
    def sing():
        pass

现在Knight("Galahad")继续有效但返回KnightOfTheRoundTableisinstance(Knight("Robin"), Knight)返回True,对任何其他子类实例的isinstance(x, Knight)检查也是如此。

答案 1 :(得分:2)

__new__混淆的解决方案大多有效,但它有一个缺点,Knight(...)没有给你Knight,而Knight只是一个ABC。< / p>

BaseKnight有点干净,但是你有问题

  检查isinstance(x, Knight)并且应该对任何骑士工作的

代码可能需要更改以检查BaseKnight。

可以通过添加

进行修补
    def __subclasscheck__(object):
        return issubclass(object, BaseKnight)

Knight。你不希望这会影响Knight的子类,所以你这样做也很可怕 hack:

    @classmethod
    def __subclasscheck__(cls, object):
        if cls is Knight:
            return issubclass(object, BaseKnight)
        else:
            return ABCMeta.__subclasscheck__(cls, object)

from abc import ABCMeta, abstractmethod

class BaseKnight(metaclass=ABCMeta):  # Python 3 syntax
    def __str__(self):
        return "Sir {} of {}".format(self.name, self.castle)

    @abstractmethod
    def sing():
        pass

这是基础,然后您具有重定向Knightisinstance检查的具体issubclass

class Knight(BaseKnight):
    def __init__(self, name, castle="Camelot"):
        self._name = name

    @abstractmethod
    def sing(self):
        return ["Can't read my,",
                "Can't read my",
                "No he can't read my poker face"]

    @classmethod
    def __subclasscheck__(cls, object):
        if cls is Knight:
            return issubclass(object, BaseKnight)
        else:
            return ABCMeta.__subclasscheck__(cls, object)

最后,一些测试:

class KnightOfTheRoundTable(Knight):
    def __init__(self, name):
        self.name = name

    @property
    def castle(self):
        return "Camelot"

    def sing():
        return ["We're knights of the round table",
                "We dance whenever we're able"]

class DuckKnight(BaseKnight):
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return "Knight Quacker of Quack"

    def sing():
        return ["Quack!"]

isinstance(KnightOfTheRoundTable("John"), Knight)
#>>> True

isinstance(DuckKnight("Quacker"), Knight)
#>>> True

通过编辑以委托回ABCMeta.__subclasscheck__(cls, object)我不再是 解决方案特别好。

值得注意的是,

  • 您可能比使用Knight对比具体isinstance类型(在ABC之前)实例化Knight方式。将疯狂行为局限于狭小空间是有道理的。

  • 更改isinstance表示您无需更改Knight,这意味着对代码库的损害更小。