__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