第九章 特殊方法,属性和迭代器

xiaoxiao2021-02-27  263

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) #关闭后,yield 2和yield 3语句将不再起作用 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: #如果是字符串,那么手动抛出TypeError。 if isinstance(nested, str): raise TypeError for sublist in nested: #yield flatten(sublist) for element in flatten(sublist): #yield element print('got:', element) except TypeError: #print('here') 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 是因为要返回的是一整个元组
转载请注明原文地址: https://www.6miu.com/read-8655.html

最新回复(0)