假设您有一个具体的类
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
检查?
答案 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")
继续有效但返回KnightOfTheRoundTable
。 isinstance(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
这是基础,然后您具有重定向Knight
和isinstance
检查的具体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
,这意味着对代码库的损害更小。