zoukankan      html  css  js  c++  java
  • 流畅的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.11python.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.11python.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.11lib
    andom.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.11python.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.11python.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.11python.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.11python.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.11python.exe E:/py_prj/fluent_python/chapter11.py
    Cat eat....
    miao...
    继承和注册这两种方法有什么区别呢:区别在于通过继承能够看到继承抽象基类的所有类,而用注册的方法却看不到。
    for sc in Animal.__subclasses__():
        print sc.__name__
    E:python2.7.11python.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.11python.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
  • 相关阅读:
    【POJ】【2104】区间第K大
    【BZOJ】【2595】【WC2008】游览计划
    【BZOJ】【1036】树的统计
    【BZOJ】【2038】小Z的袜子
    我的博客~
    CSS选择器
    CSS样式表分类
    python奇闻杂技03 三元表达式示例,函数定义示例,七段彩码管绘制示例
    python奇闻杂技02 time模块,文本进度条示例,数字类型操作,字符串操作
    python奇闻杂技01 turtle学习
  • 原文地址:https://www.cnblogs.com/zhanghongfeng/p/7220533.html
Copyright © 2011-2022 走看看