流畅的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学习笔记:第十一章:抽象基类的更多相关文章
- python cookbook第三版学习笔记十六:抽象基类
假设一个工程中有多个类,每个类都通过__init__来初始化参数.但是可能有很多高度重复且样式相同的__init__.为了减少代码.我们可以将初始化数据结构的步骤归纳到一个单独的__init__函数中 ...
- 流畅的python学习笔记:第二章
第二章开始介绍了列表这种数据结构,这个在python是经常用到的结构 列表的推导,将一个字符串编程一个列表,有下面的2种方法.其中第二种方法更简洁.可读性也比第一种要好 str='abc' strin ...
- 流畅的python学习笔记:第一章
这一章中作者简要的介绍了python数据模型,主要是python的一些特殊方法.比如__len__, __getitem__. 并用一个纸牌的程序来讲解了这些方法 首先介绍下Tuple和nametup ...
- 流畅的python学习笔记第七章:装饰器
装饰器就如名字一样,对某样事物进行装饰过后然后返回一个新的事物.就好比一个毛坯房,经过装修后,变成了精装房,但是房子还是同样的房子,但是模样变了. 我们首先来看一个函数.加入我要求出函数的运行时间.一 ...
- 流畅的python学习笔记:第九章:符合python风格的对象
首先来看下对象的表现形式: class People(): def __init__(self,name,age): self.name=name self.a ...
- Python学习笔记(十一)
Python学习笔记(十一): 生成器,迭代器回顾 模块 作业-计算器 1. 生成器,迭代器回顾 1. 列表生成式:[x for x in range(10)] 2. 生成器 (generator o ...
- 【python学习笔记】7.更加抽象
[python学习笔记]7.更加抽象 类的定义就是执行代码块 在内存保存一个原始实例,可以通过类名来访问 类的实例化,是创建一个原始实例的副本, 并且所有成员变量与原始实例绑定 通过修改实例变量,可以 ...
- qml学习笔记(二):可视化元素基类Item详解(上半场anchors等等)
原博主博客地址:http://blog.csdn.net/qq21497936本文章博客地址:http://blog.csdn.net/qq21497936/article/details/78516 ...
- python面对对象编程---------6:抽象基类
抽象基本类的几大特点: 1:要定义但是并不完整的实现所有方法 2:基本的意思是作为父类 3:父类需要明确表示出那些方法的特征,这样在写子类时更加简单明白 用抽象基本类的地方: 1:用作父类 2:用作检 ...
随机推荐
- 2016集训测试赛(十九)Problem C: 无聊的字符串
Solution 傻X题 我的方法是建立后缀后缀树, 然后在DFS序列上直接二分即可. 关键在于如何得到后缀树上每个字符对应的字节点: 我们要在后缀自动机上记录每个点在后缀树上对应的字母. 考虑如何实 ...
- SecureCRT保持连接,不会过一段时间关闭Session
[Options]->[Global Options]->[General]->[Default Session]点击[Edit default settings]按钮,在[Term ...
- VS2010中打开VS2012项目的方法
VS2012中对C#的支持度非常好,不管是编写方便程度(不需要插件就能高亮代码及代码自动提示功能),还对MFC的一些功能优化很多. 修改两个工程文件就把VS2012的项目移植到VS2010中去的方法如 ...
- springboot + mybatis配置多数据源示例
转:http://www.jb51.net/article/107223.htm 在实际开发中,我们一个项目可能会用到多个数据库,通常一个数据库对应一个数据源. 代码结构: 简要原理: 1)Datab ...
- mongodb分片片键的选择(持续更新中)
首先要了解项目的情况,检查使用情况 对集合进行分片时,要选择一个或者两个字段拆分数据,这个键叫做片键 一旦拥有对个分片,在修改片键几乎是不肯能的事情,因此选择合适的片键是非常重要的. 对集合分片之前要 ...
- URAL1306 Sequence Median(卡内存神题)
给出n个数,n<=250000,求这n个数的中位数,内存限制1mb 卡内存的神题,用数组存下来刚好1mb,再加上执行时消耗内存.立即爆. 因此我们用优先队列存储一半的数. 网上的某些代码,用pr ...
- vuex mapState使用
<template> <div> {{count}} <button @click="handleIncrease">+5</button ...
- Odoo11 重大改变
Table of Contents 新特性 Activity 项目子任务 组织架构 地址 域 widget 功能重构 Quant 份 procurement 补货 自动动作 动作绑定 去掉了stock ...
- apue学习笔记(第十五章 进程间通信)
本章将说明进程之间相互通信的其它技术----进程间通信(IPC) 管道 管道只能在具有公共祖先的两个进程之间只用.通常,一个管道由一个进程创建,在进程调用fork后,这个管道就能在父进程和子进程之间使 ...
- .netMVC:Web页面向后台提交数据的方式和选择
众所周知Web前端页面主要由HTML/CSS/Javascript组成,当要通过与用户的交互实现各种功能时,就需要向后台提交一些数据或者操作.在Web世界里各种实现眼花缭乱,但究其根本,不外乎三种方式 ...