2014-10-20 49 views
3

假设你有一个具体的类制作一个具体的类抽象,保留构造

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

使现有类的基类,但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)检查任何其他子类实例。

2

您的解决方案与__new__搞乱大部分工作,但它有缺点,其中Knight(...)不给你一个Knight,并且Knight只是一个ABC。

BaseKnight是有点清洁,但你有问题

代码检查isinstance(x, Knight),并应在任何工作,骑士可能必须改变以检查BaseKnight代替。

这可以通过添加

def __subclasscheck__(object): 
     return issubclass(object, BaseKnight) 

Knight进行修补。你不希望这影响Knight的子类,虽然如此,你这样做还可怕黑客:

@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重定向isinstanceissubclass检查:

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针对具体Knight型(前ABC)更多。将疯狂的行为限制在狭小空间是有道理的。

  • 更改isinstance意味着您不必更改Knight,这意味着对代码库的损害较小。

+0

+1为另一个有效的解决方案,但我不明白为什么从'Knight .__ new__'返回'Knight'的子类是如此可怕。毕竟,一个子类实例*是一个'Knight',对吧?有什么地方可能会出现错误吗? – 2014-10-21 06:50:13

+0

我不得不改变一些事情来修复我忽略的错误。现在并没有更好的方法,但我确实给了这个方法两个好处。 – Veedrac 2014-10-21 13:10:50