流暢的python學習筆記:第十一章:抽象基類


__getitem__實現可迭代對象。要將一個對象變成一個可迭代的對象,通常都要實現__iter__。但是如果沒有__iter__的話,實現了__getitem__也可以實現迭代。我們還是用第一章撲克牌的例子來看下
class FrenchDeck:
    ranks=[str(n) for n in range(2,11)] + list('JQKA')
    suits='spades diamonds clubs hearts'.split()
    def __init__(self):
        self._cards=[Card(rank,suit) for suit in self.suits for rank in self.ranks]
    def __len__(self):
        return len(self._cards)
    def __getitem__(self, position):
        return self._cards[position]

if __name__ == "__main__":
    deck=FrenchDeck()
    for d in deck:
        print d
E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter11.py
Card(rank='2', suit='spades')
Card(rank='3', suit='spades')
Card(rank='4', suit='spades')
Card(rank='5', suit='spades')
Card(rank='6', suit='spades')
Card(rank='7', suit='spades')
Card(rank='8', suit='spades')
Card(rank='9', suit='spades')
Card(rank='10', suit='spades')
Card(rank='J', suit='spades')
Card(rank='Q', suit='spades')
Card(rank='K', suit='spades')
Card(rank='A', suit='spades')
Card(rank='2', suit='diamonds')
Card(rank='3', suit='diamonds')
Card(rank='4', suit='diamonds')
Card(rank='5', suit='diamonds')
Card(rank='6', suit='diamonds')
Card(rank='7', suit='diamonds')
Card(rank='8', suit='diamonds')
Card(rank='9', suit='diamonds')
Card(rank='10', suit='diamonds')
Card(rank='J', suit='diamonds')
Card(rank='Q', suit='diamonds')
Card(rank='K', suit='diamonds')
Card(rank='A', suit='diamonds')
Card(rank='2', suit='clubs')
Card(rank='3', suit='clubs')
Card(rank='4', suit='clubs')
Card(rank='5', suit='clubs')
Card(rank='6', suit='clubs')
Card(rank='7', suit='clubs')
Card(rank='8', suit='clubs')
Card(rank='9', suit='clubs')
Card(rank='10', suit='clubs')
Card(rank='J', suit='clubs')
Card(rank='Q', suit='clubs')
Card(rank='K', suit='clubs')
Card(rank='A', suit='clubs')
Card(rank='2', suit='hearts')
Card(rank='3', suit='hearts')
Card(rank='4', suit='hearts')
Card(rank='5', suit='hearts')
Card(rank='6', suit='hearts')
Card(rank='7', suit='hearts')
Card(rank='8', suit='hearts')
Card(rank='9', suit='hearts')
Card(rank='10', suit='hearts')
Card(rank='J', suit='hearts')
Card(rank='Q', suit='hearts')
Card(rank='K', suit='hearts')
Card(rank='A', suit='hearts')

從輸出結果可以看到,通過for d in deck迭代的方式也能遍歷整個_card數組。迭代器環境會先嘗試__iter__方法,在嘗試__getitem__.也就是如果對象不支持迭代協議,就會嘗試索引運算。迭代環境是通過調用內置函數iter去嘗試__iter__方法來實現的,這種方法返回一個迭代器對象,如果提供Python就會重復調用這個迭代器對象的next方法,知道發生StopIteration異常,如果沒找到這類__iter__方法,Python就會改用__getitem__機制,通過偏移量重復索引,直至發生IndexError異常

但是這個FrenchDeck有個問題:無法洗牌,從上面的結果來看,發票的順序都是按照每個花色依次排列好的。那么如何洗牌了。這就需要用到隨機數的方法了
我們用random.shuffle的方法來做隨機數:
list=[1,2,3]
random.shuffle(list)
print list
得到的結果是[3, 2, 1]
Shuffle的實現代碼如下:
if random is None:
    random = self.random
_int = int
for i in reversed(xrange(1, len(x))):
    # pick an element in x[:i+1] with which to exchange x[i]
   
j = _int(random() * (i+1))
    x[i], x[j] = x[j], x[i]
其實也比較簡單,就是通過產生隨機數來顛倒列表中的排列。那么是否可以根據這個函數來對撲克牌進行隨機排列呢。我們來試下:
deck=FrenchDeck()
random.shuffle(deck)
for d in deck:
    print d
E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter11.py
Traceback (most recent call last):
  File "E:/py_prj/fluent_python/chapter11.py", line 32, in <module>
    random.shuffle(deck)
  File "E:\python2.7.11\lib\random.py", line 291, in shuffle
    x[i], x[j] = x[j], x[i]
AttributeError: FrenchDeck instance has no attribute '__setitem__'
提示沒有實現__setitem__, 為什么會錯誤呢。在Traceback里面已經寫得很清楚了,因為:x[i], x[j] = x[j], x[i]。__getitem__是在deck[i]的時候調用,但是要賦值的時候比如deck[i]=value的時候得調用__setitem__
因此我們需要加上__setitem__:
def __setitem__(self, key, value):
    self._cards[key]=value
再次運行得到結果如下:可以看到撲克牌完全是隨機排列的了
E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter11.py
Card(rank='10', suit='diamonds')
Card(rank='3', suit='hearts')
Card(rank='4', suit='diamonds')
Card(rank='A', suit='clubs')
Card(rank='4', suit='spades')
Card(rank='K', suit='clubs')
Card(rank='8', suit='clubs')
Card(rank='2', suit='clubs')
Card(rank='8', suit='hearts')
Card(rank='7', suit='diamonds')
Card(rank='5', suit='hearts')
Card(rank='10', suit='hearts')
Card(rank='6', suit='hearts')
Card(rank='Q', suit='clubs')
Card(rank='J', suit='hearts')
Card(rank='10', suit='spades')
Card(rank='9', suit='spades')
Card(rank='2', suit='diamonds')
Card(rank='2', suit='spades')
Card(rank='10', suit='clubs')
Card(rank='3', suit='clubs')
Card(rank='5', suit='spades')
Card(rank='5', suit='clubs')
Card(rank='Q', suit='hearts')
Card(rank='3', suit='diamonds')
Card(rank='J', suit='spades')
Card(rank='7', suit='spades')
Card(rank='8', suit='spades')
Card(rank='6', suit='spades')
Card(rank='Q', suit='diamonds')
Card(rank='9', suit='diamonds')
Card(rank='8', suit='diamonds')
Card(rank='4', suit='clubs')
Card(rank='6', suit='diamonds')
Card(rank='9', suit='clubs')
Card(rank='K', suit='spades')
Card(rank='4', suit='hearts')
Card(rank='J', suit='diamonds')
Card(rank='5', suit='diamonds')
Card(rank='6', suit='clubs')
Card(rank='A', suit='spades')
Card(rank='9', suit='hearts')
Card(rank='K', suit='diamonds')
Card(rank='7', suit='clubs')
Card(rank='A', suit='hearts')
Card(rank='Q', suit='spades')
Card(rank='A', suit='diamonds')
Card(rank='J', suit='clubs')
Card(rank='3', suit='spades')
Card(rank='2', suit='hearts')
Card(rank='7', suit='hearts')
Card(rank='K', suit='hearts')
抽象基類:
抽象基類的作用類似於JAVA中的接口。在接口中定義各種方法,然后繼承接口的各種類進行具體方法的實現。抽象基類就是定義各種方法而不做具體實現的類,任何繼承自抽象基類的類必須實現這些方法,否則無法實例化。
那么抽象基類這樣實現的目的是什么呢? 假設我們在寫一個關於動物的代碼。涉及到的動物有鳥,狗,牛。首先鳥,狗,牛都是屬於動物的。既然是動物那么肯定需要吃飯,發出聲音。但是具體到鳥,狗,牛來說吃飯和聲音肯定是不同的。
需要具體去實現鳥,狗,牛吃飯和聲音的代碼。概括一下抽象基類的作用:定義一些共同事物的規則和行為。
來看下具體的代碼實現,定義一個抽象基類的簡單方法如下: 首先在Dog,Bird,Cow都繼承自Animal。 在Animal中定義了eat和voice兩個方法
任何從Animal中繼承的子類都必須實現eat和voice方法。否則調用的時候會報錯class Animal(object):
     def eat(self):
        raise NotImplementedError
    def voice(self):
        raise NotImplementedError

class Dog(Animal):
    def voice(self):
        print 'wow....'

class
Bird(Animal):
    def voice(self):
        print 'jiji....'

class
Cow(Animal):
    def voice(self):
        print 'Oh.....'

if
__name__ == "__main__":
    d=Dog()
    d.voice()
d.eat()
執行結果如下, voice可以正常執行,但是eat卻報錯了
E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter11.py
wow....
Traceback (most recent call last):
  File "E:/py_prj/fluent_python/chapter11.py", line 54, in <module>
    d.eat()
  File "E:/py_prj/fluent_python/chapter11.py", line 33, in eat
    raise NotImplementedError
NotImplementedError
這樣實現有個缺點,就是只有子類調用eat方法的時候才會報錯。子類是可以正常實例化的。但是你能夠想象鳥,狗,牛不會吃飯么? 如果不會吃飯那肯定不算是動物了。所以正常的實現應該是如果沒有實現eat方法,實例化就應該是失敗的。那么這里就要用到抽象基類的一般使用方法.代碼修改如下:
Import abc
class Animal(object):
    __metaclass__ = abc.ABCMeta
    @abc.abstractmethod
    def eat(self):
        return
   
@abc.abstractmethod
    def voice(self):
        return
if __name__ == "__main__":
    d=Dog()
結果如下,代碼無法實例化,提示沒有實現eat方法。這樣就完美的達到了我們的目的。
E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter11.py
Traceback (most recent call last):
  File "E:/py_prj/fluent_python/chapter11.py", line 56, in <module>
    d=Dog()
TypeError: Can't instantiate abstract class Dog with abstract methods eat
 
完整代碼修改如下class Animal(object):
    __metaclass__ = abc.ABCMeta
    @abc.abstractmethod
    def eat(self):
        return
   
@abc.abstractmethod
    def voice(self):
        return

class
Dog(Animal):
    def voice(self):
        print 'wow....'
    def
eat(self):
        print 'Dog eat....'

class
Bird(Animal):
    def voice(self):
        print 'jiji....'
    def
eat(self):
        print 'Bird eat....'

class
Cow(Animal):
    def voice(self):
        print 'Oh.....'
    def
eat(self):
        print 'Cow eat....'

if
__name__ == "__main__":
    d=Dog()
    b=Bird()
    c=Cow()
    d.voice()
    d.eat()
    b.voice()
    b.eat()
    c.voice()
    c.eat()
E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter11.py
wow....
Dog eat....
jiji....
Bird eat....
Oh.....
Cow eat....
除了繼承,還有一種注冊的方法可以將類和抽象基類關聯起來:Animal.register(Cat)
class Cat(object):
    def voice(self):
        print 'miao.....'
    def
eat(self):
        print 'Cat eat....'

Animal.register(Cat)

if __name__ == "__main__":
    c=Cat()
    c.eat()
    c.voice()
E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter11.py
Cat eat....
miao...
繼承和注冊這兩種方法有什么區別呢:區別在於通過繼承能夠看到繼承抽象基類的所有類,而用注冊的方法卻看不到。
for sc in Animal.__subclasses__():
    print sc.__name__
E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter11.py
Dog
Bird
Cow
執行結果里面,只有Dog,Bird,Cow並沒有Cat
最后介紹一種抽象子類的注冊方式:__subclasshook__
class Animal(object):
    __metaclass__ = abc.ABCMeta
    @abc.abstractmethod
    def eat(self):
        return
   
@abc.abstractmethod
    def voice(self):
        return
   
@classmethod
    def __subclasshook__(cls, c):
        if cls is Animal:  :⑴
            if any("eat" in cat.__dict__ for cat in c.__mro__):⑵
                return True    
        return NotImplementedError  ⑶
 
class Cat(object):
    def voice(self):
        print 'miao.....'
    def
eat(self):
        print 'Cat eat....'
 
if __name__ == "__main__":
    c=Cat()
    print isinstance(Cat(),Animal)
    print Animal.__subclasshook__(Cat)
E:\python2.7.11\python.exe E:/py_prj/fluent_python/chapter11.py
True
True
 
 
 
         (1)
  
  
  
          首先判斷cls是否屬於Animal,在這里__subclasshook__被classmethod修飾,證明是一個對象的方法,因此cls肯定等於Animal
 
 
 
         (2)
  
  
  
          首先得到c.__mro__. 當調用isinstance(Cat(),Animal)或者Animal.__subclasshook__(Cat)的時候,c就是Cat,c.__mro__就是得到Cat以及Cat的父類。c.__mro__=(<class '__main__.Cat'>, <type 'object'>)。  然后看下在Cat以及Cat的父類object的屬性中是否有eat方法的實現,這里可以用eat或者voice方法來判斷。如果是,則返回True
 
 
 
         (3)
  
  
  
          否則返回NotImplementedError


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM