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

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()


题外话:

  1. 内置函数iter()仅仅是调用了对象的__iter__()方法,所以list对象内部一定存在方法__iter__()
  2. 内置函数next()仅仅是调用了对象的__next__()方法,所以list对象内部一定不存在方法__next__(),但是Itrator中一定存在这个方法。
  3. 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;

 

如果遇到return,如果在执行过程中 return,则直接抛出 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'))

执行流程:

  1. 通过g.send(None)或者next(g)可以启动生成器函数,并执行到第一个yield语句结束的位置。(挂起生成器
    此时,执行完了yield语句,但是没有给receive赋值。
    yield value会输出初始值0
    注意:在启动生成器函数时只能send(None),如果试图输入其它的值都会得到错误提示信息。
  2. 通过g.send('aaa'),会传入aaa,并赋值给receive,然后计算出value的值,并回到while头部,执行yield value语句有停止。
    此时yield value会输出"got: aaa",然后挂起。
  3. 通过g.send(3),会重复第2步,最后输出结果为"got: 3"
  4. 当我们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

解释:

  1. print(next(g)):会输出normal value,并停留在yield 'normal value 2'之前。
  2. 由于执行了g.throw(ValueError),所以会跳过所有后续的try语句,也就是说yield 'normal value 2'不会被执行,然后进入到except语句,打印出we got ValueError here。
    然后再次进入到while语句部分,消耗一个yield,所以会输出normal value。
  3. print(next(g)),会执行yield 'normal value 2'语句,并停留在执行完该语句后的位置。
  4. 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 是因为要返回的是一整个元组






















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值