1. 程式人生 > >Python抽象類

Python抽象類

抽象類

抽象類描述了相關的一組類的通用的特徵和行為。在客戶的應用程式中,抽象類是不能正常例項化的,它的子類叫做具體類,這表明它們才是真正用來在客戶應用程式中建立物件的類。

下面是本文的集合框架圖


設計一個AbstractBag類

前面我們在Python 基於陣列、連結串列實現的包介面中以不同方法實現了包介面。然後又在Python繼承中實現了一個繼承ArrayBag類的子類ArraySortedBag類。現在我們需要在ArrayBag類和LinkedBag類之上實現一個抽象類來去除冗餘程式碼。

包類中最顯而易見的冗餘方法是直接呼叫其它方法而沒有直接訪問例項變數的那些方法,這包括isEmpty、__str__、__add__和__eq__等方法。冗餘的例項變數有點難以識別。包類使用了兩個例項變數,分別是self._items和self._size。每個類中,self._items引用的是資料結構的一個不同的資料型別,相反,self._size在每一個包類中都引用了一個整數值。因此只有self._size是一個冗餘的例項變數。

由於__len__方法訪問了self._size而不是self._items,因此它也被視為冗餘方法。

我們可以將包類中冗餘的方法刪除,並且將它們放置到一個叫做AbstractBag的新類中,然後,包類會通過繼承變為AbstractBag的子類,從而訪問這些方法。

注意:通常一個類的方法和變數對其所有的子孫類都是適用的。

AbstractBag類中的__init__方法

(1)引入變數self._size並將其初始化為0

(2)如果需要的話,將源集合中的項新增到self中

因此,我們需要刪除初始化變數self._items的行,這是子類的職責。

class AbstractBag(object):
    """An abstract bag implementation."""

    # Constructor
    def __init__(self, sourceCollection = None):
        """Sets the initial state of self, which includes the contents of sourceCollection, if it's present."""
        self._size = 0
        if sourceCollection:
            for item in sourceCollection:
                self.add(item)
AbstractBag子類的__init__方法修改

ArrayBag的__init__方法修改(LinkedBag的__init__方法修改類似,這裡忽略)

這個方法仍然負責將self._items設定為一個新的陣列,然後在通過AbstractBag中的__init__方法來初始化包的大小並且在需要的是時候新增源集合的項。

from arrays import Array
from abstractbag import AbstractBag

class ArrayBag(AbstractBag):
    """An array-based bag implementation."""

    # Class variable
    DEFAULT_CAPACITY = 10

    # Constructor
    def __init__(self, sourceCollection = None):
        """Sets the initial state of self, which includes the contents of sourceCollection, if it's present."""
        self._items = Array(ArrayBag.DEFAULT_CAPACITY)
        AbstractBag.__init__(self, sourceCollection)

這裡需要注意__init__中編寫的兩條語句的順序,在執行超類中的構造方法之前,將self._items初始化為新的陣列,以便有地方儲存新增到新包中的任何項。

AbstractBag中的__add__方法泛化

ArrayBag類中的__add__方法

    def __add__(self,other):
        """Returns a new bag containing the contents of self and other."""
        result = ArrayBag(self)
        for item in other:
            result.add(item)
        return result

如果直接將這個__add__方法複製到AbstractBag類中,然後使用測試函式來測試的話,+運算子使用了AbstractBag中的__add__方法,這會引發一個異常。因為這裡__add__方法試圖建立一個ArrayBag的一個例項來儲存其結果,但是你真正想要的並不是一個特定的例項,而是一個self型別的一個例項,而不用管這個型別是什麼。要解決這個問題,我們使用Python中的type函式來獲取self的型別。

    def __add__(self, other):
        """Returns a new bag containing the contents of self and other."""
        result = type(self)(self)
        for item in other:
            result.add(item)
        return result

完整的抽象類AbstractBag程式碼如下
class AbstractBag(object):
    """An abstract bag implementation."""

    # Constructor
    def __init__(self, sourceCollection = None):
        """Sets the initial state of self, which includes the
        contents of sourceCollection, if it's present."""
        self._size = 0
        if sourceCollection:
            for item in sourceCollection:
                self.add(item)

    # Accessor methods
    def isEmpty(self):
        """Returns True if len(self) == 0, or False otherwise."""
        return len(self) == 0
    
    def __len__(self):
        """Returns the number of items in self."""
        return self._size

    def __str__(self):
        """Returns the string representation of self."""
        return "{" + ", ".join(map(str, self)) + "}"

    def __add__(self, other):
        """Returns a new bag containing the contents
        of self and other."""
        result = type(self)(self)
        for item in other:
            result.add(item)
        return result

    def __eq__(self, other):
        """Returns True if self equals other,
        or False otherwise."""
        if self is other: return True
        if type(self) != type(other) or \
           len(self) != len(other):
            return False
        for item in self:
            if not item in other:
                return False
        return True
完整的繼承抽象類AbstractBag的ArrayBag類的程式碼如下
from arrays import Array
from abstractbag import AbstractBag

class ArrayBag(AbstractBag):
    """An array-based bag implementation."""

    # Class variable
    DEFAULT_CAPACITY = 10

    # Constructor
    def __init__(self, sourceCollection = None):
        """Sets the initial state of self, which includes the
        contents of sourceCollection, if it's present."""
        self._items = Array(ArrayBag.DEFAULT_CAPACITY)
        AbstractBag.__init__(self, sourceCollection)

    # Accessor methods
    def __iter__(self):
        """Supports iteration over a view of self."""
        cursor = 0
        while cursor < len(self):
            yield self._items[cursor]
            cursor += 1

    # Mutator methods
    def clear(self):
        """Makes self become empty."""
        self._size = 0
        self._items = Array(ArrayBag.DEFAULT_CAPACITY)

    def add(self, item):
        """Adds item to self."""
        # Check array memory here and increase it if necessary
        self._items[len(self)] = item
        self._size += 1

    def remove(self, item):
        """Precondition: item is in self.
        Raises: KeyError if item in not in self.
        Postcondition: item is removed from self."""
        # Check precondition and raise if necessary
        if not item in self:
            raise KeyError(str(item) + " not in bag")
        # Search for the index of the target item
        targetIndex = 0
        for targetItem in self:
            if targetItem == item:
                break
            targetIndex += 1
        # Shift items to the left of target up by one position
        for i in range(targetIndex, len(self) - 1):
            self._items[i] = self._items[i + 1]
        # Decrement logical size
        self._size -= 1
        # Check array memory here and decrease it if necessary        

所有集合的一個抽象類

如果你檢視AbstractBag類的程式碼,你會發現,幾乎所有的方法(包括__init__方法)都執行其他的方法或函式,或者直接訪問變數self._size,它們都不會提及包類。除了__str__方法(它使用花括號建立了一個字串)和__eq__方法(它並不比較給定位置的成對的項)。更好的建議是:將這些方法和資料放到一個更加通用的類中,這樣的類叫做AbstractCollection。


AbstractCollection類負責引入和初始化self._size變數,所有集合類都使用這個變數。AbstractCollection的__init__方法也可以將源集合中的項加入到self。這個類還包含了所有集合可用的最通用的方法:isEmpty、__len__和__add__,這裡的“最通用”是指它們的實現不需要由子類來修改。

最後AbstractCollection還包含了__str__和__eq__方法的預設實現,它們在AbstractBag中的當前形式適合於無序的集合,但是大多數集合類很可能是線性的而不是無序的。因此,這兩個方法在AbstractBag中保持不變,但是在AbstractCollection中提供了新的實現。新的__str__方法用方括號來分割字串,新的__eq__方法比較給定位置成對的項,AbstractCollection的子類仍然能夠自由定製__str__和__eq__以滿足自己的需要。

建立AbstractCollection類和AbstractBag類的步驟:

(1)從之前實現的AbstractBag類中複製程式碼,然後將該類命名為AbstractCollection類

(2)通過刪除父類中對__init__的呼叫,修改__init__方法

(3)修改__str__和__eq__方法以提供合理的預設行為

(4)從AbstractBag類中刪除isEmpty、__len__和__add__方法

AbstractCollection類
class AbstractCollection(object):
    """An abstract collection implementation."""

    # Constructor
    def __init__(self, sourceCollection = None):
        """Sets the initial state of self, which includes the
        contents of sourceCollection, if it's present."""
        self._size = 0
        if sourceCollection:
            for item in sourceCollection:
                self.add(item)

    # Accessor methods
    def isEmpty(self):
        """Returns True if len(self) == 0, or False otherwise."""
        return len(self) == 0
    
    def __len__(self):
        """Returns the number of items in self."""
        return self._size

    def __str__(self):
        """Returns the string representation of self."""
        return "[" + ", ".join(map(str, self)) + "]"

    def __add__(self, other):
        """Returns a new bag containing the contents
        of self and other."""
        result = type(self)(self)
        for item in other:
            result.add(item)
        return result

    def __eq__(self, other):
        """Returns True if self equals other,
        or False otherwise."""
        if self is other: return True
        if type(self) != type(other) or \
           len(self) != len(other):
            return False
        otherIter = iter(other)
        for item in self:
            if item != next(otherIter):
                return False
        return True

AbstractBag類
from abstractcollection import AbstractCollection

class AbstractBag(AbstractCollection):
    """An abstract bag implementation."""

    # Constructor
    def __init__(self, sourceCollection = None):
        """Sets the initial state of self, which includes the
        contents of sourceCollection, if it's present."""
        self._size = 0
        AbstractCollection.__init__(self,sourceCollection)
    

    def __str__(self):
        """Returns the string representation of self."""
        return "{" + ", ".join(map(str, self)) + "}"


    def __eq__(self, other):
        """Returns True if self equals other,
        or False otherwise."""
        if self is other: return True
        if type(self) != type(other) or len(self) != len(other):
            return False
        for item in self:
            if not item in other:
                return False
        return True

總結

抽象類充當其它一組類所共有的資料和方法的一個庫。如果這些其它的類不是抽象類,它們就叫做具體類。

抽象類是不能例項化的。