首页 > 代码库 > 流畅python学习笔记:第十一章:抽象基类

流畅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

流畅python学习笔记:第十一章:抽象基类