流畅python学习笔记:第十一章:抽象基类
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了流畅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学习笔记:第十一章:抽象基类的主要内容,如果未能解决你的问题,请参考以下文章