9.1 准备工作
确保类是新式类
1.一些特性不会在老式类上起作用。在模块最开始放入赋值语句
__metaclass__=type 关于__metaclass__ 参见博客
2.子类化内建类(新类)
class NewStyle(object):
3.0中不用
- type是一种object, type is kind of object。即Type是object的子类。
在Python的世界中,object是父子关系的顶端,所有的数据类型的父类都是它;type是类型实例关系的顶端,所有对象都是它的实例的。它们两个的关系可以这样描述:
- object是一个type,object is and instance of type。即Object是type的一个实例。
因为type本身也是type的实例
object 无父类
type是元类,object是元类的一个实例,所以是所有类的父类
9.2 构造方法 __init__
特殊方法
当一个对象被创建后,会立即调用构造方法
如果参数可选,还可以把参数传给__init__
>>> class FooBar:
def __init__(self,value=42):
self.somevar=value
>>> f=FooBar('This is aconstructor argument')
>>> f.somevar
'This is aconstructor argument'
9.2.1 重写一般方法和特殊的构造方法
如果子类改写了超类的特殊构造方法,可能会导致超类中,一些特性没被初始化(定义),从而使得在子类中用不了这些特性。
可以调用超类的构造方法
>>> class Bird:
def __init__(self):
self.hungry=True
def eat(self):
if self.hungry:
print 'Aaaah'
else:
print 'No,thanks!'
>>> class SongBird(Bird):
def __init__(self):
self.sound='miaomiaomiao'
def sing(self):
print self.sound
>>> sb=SongBird()
>>> sb.sing()
miaomiaomiao
>>> sb.eat()
Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
sb.eat()
File "<pyshell#1>", line 5, in eat
if self.hungry:
AttributeError: SongBird instance has no attribute 'hungry' #看吧 SongBird没有hungry特性
1.调用超类构造方法的未绑定版本(一般是旧版本)
2.使用 super函数(只能在新式类中使用)super其实是一个类名,返回了一个 super对象详情见博客
1.在子类构造方法处调用超类的构造方法
>>> class SongBird(Bird):
def __init__(self):
Bird.__init__(self) #直接调用类的方法
self.sound='miaomiaomiao'
def sing(self):
print self.sound
原理:
调用实例的方法时,self会自动绑定到实例上(绑定方法),而直接调用类的方法,就不会被实例绑定。然后再把当前实例作为self传给未绑定的方法(先提供方法,再绑定实例)
2.super
当前的类和对象可以作为参数使用,调用 super返回的 对象 的任何方法 都是调用超类的方法(返回一个super对象,这个对象的任何方法都从超类调用)(查找所有超类,甚至超类的超类,直到引发AttributeError)
class SongBird(Bird):
def __init__(self):
super(SongBird,self).__init__() #3.0中也可以不带任何参数
self.sound='miaomiaomiao'
def sing(self):
print self.sound
9.3 成员访问(介绍特殊方法的使用)
常见的特殊方法的集合,来创建一个行为类似于序列或映射的对象
9.3.1 基本序列和映射的规则
protocol:规则,描述管理某种形式的行为的规则
python中只要简单地要求它遵守几个给定的规则
1.__len__(self):返回集合中所含项目的数量
2.__getitem__(self.key):返回与所给键对应的值
3.__setitem__(self,key,value):按一定方式储存和key相关的value
4.__delitem__(self,key):使用del函数时调用,删除和键相关的键
9.3.2 子类化列表,字典和字符串
只在一个操作中自定义行为,其他方法通过 继承 来实现(比如实现一个和內建列表行为类似的序列,子类化 list)
例如:
class CounterList(list):
def __init__(self,*args):
super(CounterList,self).__init__(*args)#调用list的构造方法
self.counter=0 #添加了所需的特性
def __getitem__(self,index):
self.counter+=1
return super(CounterList,self).__getitem__(index)
新类中没有重写的都可以直接使用
9.5 属性(通过访问器定义的特性称为属性)
特性
特性是对象内部的变量
对象的状态由它的特性来描述,对象的方法可以改变它的特性
可以直接从对象外部访问特性
什么叫访问器???
声明类时,通常将成员变量声明为private,以防止直接访问成员变量而引起的恶意操作。
但是,这并不是不允许访问,而是可以通过公共接口间接访问。所谓的公共接口,就
是程序设计人员在类中定义与各个私有成员变量相关的公共方法,以提高安全级别。
习惯上,称具有private访问权限的成员变量为属性,把与之对应的公共方法称为访问器。
访问器根据功能区分为读访问器(getter)和写访问器(setter)。(通过访问器,来访问私有特性)
python中使用属性(property)对特性进行访问和设置
有一些面向对象语言支持私有特性,这些特性无法从外部直接访问,需要编写getter和setter方法对这些特性进行操作
python不需要getter和seter方法,因为python中所有特性都是公开的,如果不放心使用直接访问对象的特性,可以为对象编写setter和getter 访问器方法,但是更好的解决办法是使用属性(property)
python隐藏访问器的方法,让所有特性看起来一样,这种通过访问器定义的特性被称为属性
访问器(getHeight,setHeight)能 得到 或者 重新绑定 特性,可能是类的私有属性
封装状态变量(特性),使用访问器 如:
class Rectangle:
def __init__(self):
self.width=0
self.height=0
def setSize(self,size):
self.width,self.height=size #Tuple size是元组
def getSize(self):
return self.width,self.height
使用
>>> r=Rectangle()
>>> r.width=10
>>> r.height=2
>>> r.getSize()
(10,5)
这里size是一个假想特性,通过访问器操作,有缺陷,程序员使用这个类时,不应该考虑这个类是怎么封装或操作的
因为如果将size变成一个真正的特性,width和height酒需要放到访问器中。就需要把任何使用这个类的程序重写。但是,应该能用同样的方式对待所有特性 。如果写一堆访问器又不现实
那么如何解决呢?
使用隐藏访问器方法,让所有特性看起来一样.。
创建属性的方法有两种 1.property函数(新式类) 2.使用特殊方法
9.5.1 property函数(创建属性)见博客
具有private访问权限的成员变量为属性,对应的方法为访问器
在类结尾加上
size=property(getSize,setSize)
#先取值,再赋值 隐藏访问器
property函数,把
访问器函数作为参数,创建了一个
属性,名字叫size。size可以访问之前还必须通过访问器操作的变量size
这样就无需担心怎么实现的了,可以用同样的方法处理width,height,size
使用:
>>> r=Rectangle()
>>> r.width=10
>>> r.height=2
>>> r.size #先取值
(10, 2)
>>> r.size=150,100 #再赋值
>>> r.size
(150, 100)
>>>r.width
150
可以看到,size仍然有访问器的计算,但是他们看过去就像普通的属性
property的参数4个:
1.fget(决定了产生的属性可读)
2.fset(决定了产生的属性可写)
3.fdel (决定了删除特性的方法(它不要参数))
4.doc (文档字符串)
9.5.2 静态方法和类成员方法
python中方法分为三类实例方法、类方法、静态方法
和 实现方法和新式属性的实现方法 类似的 特征
静态方法
静态方法可以由类名或对象名进行调用。
创建时分别被装入(staticmethod 类型的对象中)
类方法 类方法是只能由类名调用;(classmethod类型的对象中)
实例方法隐含的参数为类实例self,而类方法隐含的参数为类本身cls。
静态方法无隐含参数,主要为了类实例也可以直接调用静态方法。
逻辑上类方法应当只被类调用,实例方法实例调用,静态方法两者都能调用。
__metaclass__=type
class MyClass:
def smeth():
print 'This is a static method'
smeth=staticmethod(smeth) #被装入
staticmethod 类型的对象中 手动
def cmeth(cls):
print 'This is a class method of',cls
cmeth=classmethod(cmeth) #
被装入classmethod类型的对象中
不想手动包装和替换,就使用 装饰器 (decorator)(对任何可调用的对象进行包装,既能用于方法也能用于函数) @
__metaclass__=type
class MyClass:
@staticmethod #包装进staticmethod,可以指定多个装饰器
def smeth():
print 'This is a static method'
@classmethod
def cmeth(cls):
print 'This is a class method of',cls
使用
>>> MyClass.smeth()
This is a static method
>>> MyClass.cmeth()
This is a class method of <class '__main__.MyClass'>
9.5..3 __getattr__ __setattr__ 和它的朋友
拦截对象的所有特性访问是可行的,这样就可以通过旧式类实现属性,这时全部通过魔法方法来访问
* __getattribute__(self,mame) 当特性name被访问时自动被调用 (新式类)
*
__getattr__(self,name) 当特性name被访问且对象没有相应的特性(普通特性)时被调用 (通过魔法方法来访问特性)
* __setattr__(self,name,value) 当试图给特性name赋值时会被自动调用
* __delattr__(self,name) 当试图删除特性name时会被自动调用
class Rectangle:
def __init__(self):
self.width=0
self.height=0
def __setattr__(self,name,value):
if name=='size':
self.width,self.height=value
else:
#如果不是调用size,把值放入实例字典,变成属性
self.__dict__[name]=value
def __getattr__(self,name):
if name =='size':
return self.width,self.height
else:
raise AttributeError
例子:
>>> c=Rectangle()
>>> c.size=(1,2)
>>> c.op=25
>>> c.op
25
>>> c.size
(1, 2)
>>> c.a
Traceback (most recent call last):
File "<pyshell#5>", line 1, in <module>
c.a
File "C:\Users\Mojar\Desktop\open.py", line 14, in __getattr__
raise AttributeError
AttributeError
注意:__getattribute__拦截所有特性的访问包括__dict__,访问其中与self相关特性时,使用超类的__getattr__才是安全的方法、
9.6 迭代器 __iter__
这个方法是迭代规则的基础,只要对象实现了__iter__方法,对象就是可迭代的(可以直接作用于for循环的对象统称为可迭代对象(Iterable)。
实现了next的对象则是迭代器
内建函数 iter()可以从 可迭代的对象(iterable) 中获得迭代器(iterator) ,通过调用对象的__iter__方法
__iter__方法会返回一个迭代器(iterator),所谓迭代器是具有next
方法
(next方法不需要任何参数,返回他的下一个值)的对象
3.0中是__next__,next变成函数了,用于访问这个方法 next(it) 代替it.next()
题外话:
内置函数iter()仅仅是调用了对象的__iter__()方法,所以list对象内部一定存在方法__iter__()内置函数next()仅仅是调用了对象的__next__()方法,所以list对象内部一定不存在方法__next__(),但是Itrator中一定存在这个方法。
for循环内部事实上就是先调用iter()把Iterable变成Iterator在进行循环迭代的。
迭代器的优点: 1.计算一个值获取一个值,列表一次性获取所有值
class Fibs:
def __init__(self):
self.a=0
self.b=1
def next(self):
self.a,self.b=self.b,self.a+self.b #从__iter__那不断得到self,对self进行操作
return self.a
def __iter__(self): #返回本身,既迭代
return self
一般放到会在for中循环使用的对象
>>> for a in f:
if a>1000:
print a
break
1597
使用 iter()获得迭代器
>>> it=iter([1,2,3])
>>> it.next()
1
>>> it.next()
2 #如果迭代没有值可以返回,引发一个StopIteration
迭代器和可迭代对象都可以进行迭代
9.6.2 从迭代器得到序列
除了索引和分片等,其他地方都能用迭代器替换序列
>>> class TestIterator:
value=0
def next(self):
self.value+=1
if self.value>10:raise StopIteration
return self.value
def __iter__(self):
return self
使用
>>> ti=TestIterator()
>>> list(ti)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
迭代器本身不可以后退
如果要多次使用迭代器,可以使用深度复制(deepcopy)
9.7 生成器
由两部分组成:生成器的函数(语句部分,包括yield)和生成器的迭代器(函数返回的部分)
是一种用普通的函数语法定义的迭代器 (任何包含 yield 语句的函数)变成
生成器函数
生成器不会把结果保存在一个系列中,而是保存生成器的状态,在每次进行迭代时返回一个值,直到遇到StopIteration异常结束。
下面为一个可以无穷生产奇数的生成器函数。
def odd():
n=
1
while True:
yield n
n+=
2
odd_num = odd()
count =
0
for o
in odd_num:
if count >=
5:
break
print(o)
count +=
1
将一个列表变成生成器
def flatten(nested):
for sublist in nested:
for element in sublist:
yield element
#无return
不用return 返回值,而是每次产生多个值。每次生产一个值(使用yield),函数就被冻结:停在那点等待重新唤醒,唤醒后就从停止的那点开始执行
yield 与 return
在一个生成器中,如果没有return,则默认执行到函数完毕时返回StopIteration;
Python
1
2
3
4
5
6
7
8
9
10
11
>>>
def
g1
(
)
:
.
.
.
yield
1
.
.
.
>>>
g
=
g1
(
)
>>>
next
(
g
)
#第一次调用next(g)时,会在执行完yield语句后挂起,所以此时程序并没有执行结束。
1
>>>
next
(
g
)
#程序试图从yield语句的下一条语句开始执行,发现已经到了结尾,所以抛出StopIteration异常。
Traceback
(
most
recent
call
last
)
:
File
"<stdin>"
,
line
1
,
in
<
module
>
StopIteration
>>>
如果遇到return,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。
Python
1
2
3
4
5
6
7
8
9
10
11
12
>>>
def
g2
(
)
:
.
.
.
yield
'a'
.
.
.
return
.
.
.
yield
'b'
.
.
.
>>>
g
=
g2
(
)
>>>
next
(
g
)
#程序停留在执行完yield 'a'语句后的位置。
'a'
>>>
next
(
g
)
#程序发现下一条语句是return,所以抛出StopIteration异常,这样yield 'b'语句永远也不会执行。
Traceback
(
most
recent
call
last
)
:
File
"<stdin>"
,
line
1
,
in
<
module
>
StopIteration
如果在return后返回一个值,那么这个值为StopIteration异常的说明,不是程序的返回值。
生成器没有办法使用return来返回值。
>>> nested=[[1,2,3],[3,4],[5]]
>>> for num in flatten(nested):#生成器被调用 时返回一个迭代器
print num
1
2
3
3
4
5
注:生成器是包含有__iter()和next__()方法的,所以可以用for
记得第五章说过,列表推导式把[]改成()不会得到‘元组推导式’ 而是一个生成器吗?
生成器推导式( )和列表推导式 [ ] 类似,只是返回的是生成器(且可以像下面这样逐个生成)
>>> g=((i+2)**2 for i in range(2,27))
>>> g.next()
16
9.7.2递归生成器
如果要处理任意层数的嵌套,使用递归
def flatten(nested):
try:
sublist in nested:
for element in flatten(sublist): #这就是递归所在
yield element
except TypeError: #如果展开的是一个数,展开不了,就异常,输出数
yield nested
过程:
当flatten被调用时,有两种可能:基本情况和需要递归的情况
遍历所有子表再次调用 flatten() ,然后用另一个for来产生被展开的子表中的所以元素
另外,字符串不会引发TypeError,而且会产生无穷递归(因为一个字符串的第一个元素是一个长度为一的字符串)。所以不要用字符串。可以在开头加入一个检查语句
def flatten(nested):
try:#
try nested+'' #只检查nested像不像一个字符串
except TypeError:pass #非字符串,什么都不做
else:raise TypeError #是字符串,引发异常
for sublist in nested:
for element in flatten(sublist):
yield element
except TypeError:
yield nested
9.7.4 生成器方法
send :在开始运行后为生成器提供值得能力(需要一个参数)
在内部则挂起生成器(yield函数第一次被执行后),yield现在作为表达式而不是语句使用,yield返回外部通过send方法发送的值
def repeater(value):
while True:
new=(yield value)
if new is not None:value =new
使用
>>> r=repeater(45) #在内部挂起生成器
>>> r.next()
45
>>> r.next()
45
>>> r.send('hello')
'hello'
其他方法
throw:在生成器内引发一个异常(在yield表达式中)
close:用于停止生成器
close()
手动关闭生成器函数,后面的调用会直接返回StopIteration异常。
>>> def g4():
... yield 1
... yield 2
... yield 3
...
>>> g=g4()
>>> next(g)
1
>>> g.close()
>>> next(g)
Traceback (most recent call last):
File
"<stdin>", line
1,
in <module>
StopIteration
send()
生成器函数最大的特点是可以接受外部传入的一个变量,并根据变量内容计算结果后返回。 这是生成器函数最难理解的地方,也是最重要的地方,实现后面我会讲到的协程就全靠它了。
def gen():
value=
0
while True:
receive=
yield value
if receive==
'e':
break
value =
'got: %s' % receive
g=gen() #将生成器 (迭代器)赋给对象g
print(g.send(
None)) #启动生成器
print(g.send(
'aaa'))
print(g.send(
3))
print(g.send(
'e'))
执行流程:
通过g.send(None)或者next(g)可以启动生成器函数,并执行到第一个yield语句结束的位置。(挂起生成器) 此时,执行完了yield语句,但是没有给receive赋值。 yield value会输出初始值0 注意:在启动生成器函数时只能send(None),如果试图输入其它的值都会得到错误提示信息。通过g.send('aaa'),会传入aaa,并赋值给receive,然后计算出value的值,并回到while头部,执行yield value语句有停止。 此时yield value会输出"got: aaa",然后挂起。通过g.send(3),会重复第2步,最后输出结果为"got: 3"
当我们g.send('e')时,程序会执行break然后推出循环,最后整个函数执行完毕,所以会得到StopIteration异常。 最后的执行结果如下:
0
got: aaa
got: 3
Traceback (most recent
call last):
File "h.py", line
14,
in <
module>
print(
g.send(
'e'))
StopIteration
throw()
用来向生成器函数送入一个异常,可以结束系统定义的异常,或者自定义的异常。 throw()后直接跑出异常并结束程序,或者消耗掉一个yield,或者在没有下一个yield的时候直接进行到程序的结尾。
def gen():
while True:
try:
yield 'normal value'
yield 'normal value 2'
print(
'here')
except ValueError:
print(
'we got ValueError here')
except TypeError:
break
g=gen()
print(next(g))
print(g.throw(ValueError))
print(next(g))
print(g.throw(TypeError))
输出结果为:
normal
value
we got ValueError here
normal
value
normal
value 2
Traceback (most recent
call last):
File "h.py", line
15,
in <
module>
print(g.throw(TypeError))
StopIteration
解释:
print(next(g)):会输出normal value,并停留在yield 'normal value 2'之前。由于执行了g.throw(ValueError),所以会跳过所有后续的try语句,也就是说yield 'normal value 2'不会被执行,然后进入到except语句,打印出we got ValueError here。 然后再次进入到while语句部分,消耗一个yield,所以会输出normal value。print(next(g)),会执行yield 'normal value 2'语句,并停留在执行完该语句后的位置。g.throw(TypeError):会跳出try语句,从而print('here')不会被执行,然后执行break语句,跳出while循环,然后到达程序结尾,所以跑出StopIteration异常。
下面给出一个综合例子,用来把一个多维列表展开,或者说扁平化多维列表)
def flatten(nested):
try:
if isinstance(nested, str):
raise TypeError
for sublist
in nested:
for element
in flatten(sublist):
print(
'got:', element)
except TypeError:
yield nested
L=[
'aaadf',[
1,
2,
3],
2,
4,[
5,[
6,[
8,[
9]],
'ddf'],
7]]
for num
in flatten(L):
print(num)
如果理解起来有点困难,那么把print语句的注释打开在进行查看就比较明了了。
9.7.5 模拟生成器
如何使用普通的函数模拟生成器(不能生成一无限生成器)
在函数头加入 result=[]
把yield some_expression这种形式的代码替换成result.append(some_expression)
最后在函数的末尾加入
return result
9.8 八皇后问题
使用生成器解决八皇后问题
1.状态表示:使用元组或者列表来记录皇后的位置状态:需要一个小于8的状态元组
2.寻找冲突:定义一个函数,来判断冲突
3.基本情况:如果剩最后一个皇后,遍历所有位置。使用生成器,没冲突就输出
4.需要递归的情况:得到底层皇后的位置,作为一个元组返回,加到前面皇后位置信息的元组中。再把信息传给后面的元组 (pos,)+result
def conflict(state,nextX):#判断底层皇后是否和之前的冲突
nextY=len(state) #state存放之前位置信息的元组
for i in range(nextY):
if abs(state[i]-nextX) in (0,nextY-i):
return True
return False
def queens(num=8,state=()):
for pos in range(num):
if not conflict(state,pos):
if len(state)==num-1: #是最后一个皇后,遍历完成
yield (pos,)
else:
for result in queens(num,state+(pos,)): #不是,使用递归调用queens,把当前位置放到state中
yield (pos,)+result #这里不用return 是因为要返回的是一整个元组