1. 重写一般方法和特殊的构造方法
1.1 如果一个方法在B类的一个实例中被调用(或一个属性被访问),但在B类中没有找到该方法,那么会去它的超类A里面找。
1 class A: 2 ... def hello(self): 3 ... print "Hello, I'm A." 4 ... 5 >>> class B(A): 6 ... pass 7 ... 8 >>> a = A() 9 >>> b = B() 10 >>> a.hello() 11 Hello, I'm A. 12 >>> b.hello() 13 Hello, I'm A.
1.2 在子类中增加功能的最基本方式是增加方法,也可以重写一些超类的方法来自定义继承的行为
1 >>>class A: 2 3 def hello(self): 4 5 print "Hello,I'm A." 6 7 >>> class B(A): 8 9 def hello(self): 10 11 print "Hello,I'm B." 12 13 >>> a=A() 14 15 >>> b=B() 16 17 >>> a.hello() 18 19 Hello,I'm A. 20 21 >>> b.hello() 22 23 Hello,I'm B.
1.3 如果一个类的构造方法被重写,那么就需要调用超类的构造方法,否则对象可能不能被正确地初始化。
1 >>> class Bird: 2 ... def __init__(self): 3 ... self.hungry = True 4 ... def eat(self): 5 ... if self.hungry: 6 ... print 'Aaaah...' 7 ... self.hungry = False 8 ... else: 9 ... print 'No, thanks!' 10 ... 11 >>> b = Bird() 12 >>> b.eat() 13 Aaaah... 14 >>> b.eat() 15 No, thanks! 16 >>> class SongBird(Bird): 17 ... def __init__(self): 18 ... self.sound = 'Squawk!' 19 ... def sing(self): 20 ... print self.sound 21 ... 22 >>> sb = SongBird() 23 >>> sb.sing() 24 Squawk! 25 >>> sb.eat() 26 Traceback (most recent call last): 27 File "<stdin>", line 1, in <module> 28 File "<stdin>", line 5, in eat 29 AttributeError: 'SongBird' object has no attribute 'hungry'
原因:在SongBird中,构造方法被重写,但新的构造方法没有任何关于初始化hungry特性的代码。为了达到预期效果,SongBird的构造方法必须调用其超类Bird的构造方法来确保基本的初始化。有两种方法可以达到这个目的:
1). 调用超类方法的未绑定版本;
2). 使用super函数;
1.3.1 调用未绑定的超类构造方法
1 >>> class SongBird(Bird): 2 ... def __init__(self): 3 ... Bird.__init__(self) 4 ... self.sound='Squark!' 5 ... def sing(self): 6 ... print self.sound 7 ... 8 >>> sb = SongBird() 9 >>> sb.sing() 10 Squark! 11 >>> sb.eat() 12 Aaaah... 13 >>> sb.eat() 14 No, thanks!
这样做的缺点是,当一个子类的父类发生变化时(如类B的父类由A变为C时),必须遍历整个类定义,把所有的通过非绑定的方法的类名全部替换过来
1 class A: 2 def __init__(self): 3 print "enter A" 4 print "leave A" 5 class B(A): 6 def __init__(self): 7 print "enter B" 8 A.__init__(self) 9 print "leave B" 10 >>> b = B() 11 enter B 12 enter A 13 leave A 14 leave B 15 16 17 #类B的父类由A变为C,需要修改多处代码 18 class B(C): # A --> C 19 def __init__(self): 20 print "enter B" 21 C.__init__(self) # A --> C 22 print "leave B"
1.3.2 使用super函数
1). super并不是一个函数,是一个类名,形如super(B, self)事实上调用了super类的初始化函数,产生了一个super对象;
2). super类的初始化函数并没有做什么特殊的操作,只是简单记录了类类型和具体实例;
3). super(B,
self).func的调用并不是用于调用当前类的父类的func函数;
4).
Python的多继承类是通过mro的方式来保证各个父类的函数被逐一调用,而且保证每个父类函数 只调用一次(如果每个类都使用super);
5). 混用super类和非绑定的函数是一个危险行为,这可能导致应该调用的父类函数没有调用或者一个父类函数被调用多次。
6).注意super继承只能用于新式类,用于经典类时就会报错。新式类:必须有继承的类,如果没什么想继承的,那就继承object
尝试执行同样的代码,结果一致,但修改的代码只有一处,把代码的维护量降到最低,是一个不错的用法。因此在我们的开发过程中,super关键字被大量使用.
1 class A(object): # A must be new-style class 2 def __init__(self): 3 print "enter A" 4 print "leave A" 5 class B(C): # A --> C 6 def __init__(self): 7 print "enter B" 8 super(B, self).__init__() 9 print "leave B"
1 #conding = utf-8 2 __metaclass__ = type # super() only works in new style classes 3 class Bird: 4 def __init__(self): 5 self.hungry = True 6 def eat(self): 7 if self.hungry: 8 print 'Aaaah...' 9 self.hungry = False 10 else: 11 print 'No, thanks!' 12 13 class SongBird(Bird): 14 def __init__(self): 15 super(SongBird, self).__init__() # 在Python 3.0 中, super函数可以不用任何参数进行调用, 功能依然具有"魔力" 16 self.sound = 'Squark!' 17 def sing(self): 18 print self.sound 19 20 sb = SongBird() 21 sb.sing() 22 sb.eat() 23 sb.eat() 24 25 #python tt.py 26 #Squark! 27 #Aaaah... 28 #No, thanks!
1.3.3 新式类与经典类
1)B是定义的新式类。那么输入b的时候,不论是type(b),还是b.__class__都是输出的<class '__main__.B'>。
2)新式类和旧式类还有一个区别就是多继承的时候,查找要调用的方法。新式类是广度优先的查找算法,旧式类的查找方法是深度优先的
经典类继承 # -*- coding:utf-8 -*- 44 class A(): 45 """ 46 作为所有类的基类 47 """ 48 value = 1 49 50 def __init__(self, ): 51 """ 52 """ 53 54 class B(A): 55 """ 56 D的基类,继承自A 57 """ 58 59 def __init__(self, ): 60 """ 61 """ 62 63 class C(A): 64 """ 65 D的基类,继承自A 66 并且将基类中的变量值修改 67 """ 68 value = 3 69 70 def __init__(self, ): 71 """ 72 """ 73 74 class D(B, C): 75 """ 76 """ 77 78 def __init__(self, ): 79 """ 80 """ 81 82 x = D() 83 print x.value
对于以上的代码输出的结果将是1。因为对于经典类来说,在类D中调用对象value,首先要查找类D中是否有value,如果没有则按顺序查找B->A->C。它是一种广度优先的查找方式,所以首先会在基类A中查找到对象value,然后输出1。
1 新式类继承 2 3 # -*- coding:utf-8 -*- 4 class A(object): 5 """ 6 作为所有类的基类 7 """ 8 value = 1 9 10 def __init__(self, ): 11 """ 12 """ 13 14 class B(A): 15 """ 16 D的基类,继承自A 17 """ 18 19 def __init__(self, ): 20 """ 21 """ 22 23 class C(A): 24 """ 25 D的基类,继承自A 26 并且将基类中的变量值修改 27 """ 28 value = 3 29 30 def __init__(self, ): 31 """ 32 """ 33 34 class D(B, C): 35 """ 36 """ 37 38 def __init__(self, ): 39 """ 40 """ 41 42 x = D() 43 print x.value
对于这个例子输出的结果将是3。因为基类A继承于object,而所有继承于object的类都将是新式类。对于新式类来说,在类D中调用对象value,它首先查找的依然是类D。如果没有查找到的话,他会依次查找B->C->A。在我看来,它是一种按层查找的原则,会在较低层查找对象,如果没有的话再到较高层查找。所以对于这个例子来说,value在类C中被查找到,所以它的值将会是3。
2. 成员访问
2.1 基本的序列和映射规则
序列和映射是对象的集合。为了实现它们基本的行为(规则),如果对象是不可变的,那么就需要使用两个魔法方法,如果是可变的,则需要使用4个。
1) __len__(self):这个方法应该返回集合中所含项目的数量。对于序列来说,这是元素的个数;对于映射来说,是键值对的数量。如果__len__返回0(并且没有实现重写该 行为的__nozero__),对象会被当作一个布尔变量中的假值(空的列表,元组,字符串和字典也一样)进行处理。
2)__getitem__(self.key):这个方法返回与所给键对于的值。对于一个序列,键应该是一个0~n-1的整数,n是序列的长度;对于映射来说,可以使用任何种类的键。
3)__setitem__(self,key,value):这个方法应该按一定的方式存储和key相关的value,该值随后可使用__getitem__来获取。当然,只能为可以修改的对象定义这个方法。
4)__delitem__(self,key):这个方法在对一部分对象使用del语句时被调用,同时必须删除和元素相关的键。这个方法也是为可修改的对象定义的(并不是删除全部的对象, 而只删除一些需要移除的元素)。
1 #coding = utf-8 2 def checkIndex(key): 3 """ 4 所给的键时能接受的索引吗? 5 为了能被接受, 键应该是一个非负的整数. 如果它不是一个整数, 会引发TypeError; 如果它是负数, 则会引发IndexError(因为序列是无限长的) 6 """ 7 if not isinstance(key, (int, long)): raise TypeError 8 if key<0: raise IndexError 9 10 class ArithmeticSequence: 11 def __init__(self, start=0, step=1): 12 """ 13 初始化算术序列 14 起始值:序列中的第一个值 15 步长: 两个相邻值之间的差别 16 改变: 用户修改的值的字典 17 """ 18 19 self.start = start #保存开始值 20 self.step = step #保存步长值 21 self.changed = {} #没有项被修改 22 23 def __getitem__(self, key): 24 """ 25 Get an item from the arithmetic sequence. 26 """ 27 checkIndex(key) 28 try: return self.changed[key] #修改了吗? 29 except KeyError: #否则... 30 return self.start + key*self.step #...计算值 31 32 def __setitem__(self, key, value): 33 """ 34 修改算术序列中的一个项 35 """ 36 checkIndex(key) 37 self.changed[key]=value # 保存更改后的值 38 39 s = ArithmeticSequence(1, 2) 40 print s[0] 41 print s[1] 42 print s[2] 43 print s[3] 44 print s[4] 45 46 s[4]=2 47 print s[4] 48 49 print s[5]
2.2 子类化列表,字典和字符串
1 >>> class CounterList(list): 2 ... def __init__(self, *args): 3 ... super(CounterList, self).__init__(*args) 4 ... self.counter = 0 5 ... def __getitem__(self, index): 6 ... self.counter += 1 7 ... return super(CounterList, self).__getitem__(index) 8 ... 9 >>> c1 = CounterList(range(10)) 10 >>> c1 11 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 12 >>> c1.reverse() 13 >>> c1 14 [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] 15 >>> del c1[3:6] 16 >>> c1 17 [9, 8, 7, 3, 2, 1, 0] 18 >>> c1.counter 19 0 20 >>> c1[4] + c1[2] 21 9 22 >>> c1.counter 23 2
3. 属性
3.1 property函数——把类中的方法当作属性来访问
假设类的某属性,你不愿意它被悄悄地访问或改写了,希望它被访问、赋值或删除时会得到一些通知,那么我们可以使用函数property()。参数原型是:property([fget[, fset[, fdel[, doc]]]])它返回新式类(继承了object的类)的一个属性,其中:fget是属性被访问时执行的方法,fset:属性被赋值时执行的方法,fdel:属性被删除时执行的方法。
1 class C(object): 2 def __init__(self): 3 self._x = None 4 5 def getx(self): 6 print "You get my x." 7 return self._x 8 9 def setx(self, value): 10 print "You set my x." 11 self._x = value 12 13 def delx(self): 14 print "You del my x." 15 del self._x 16 17 x = property(getx, setx, delx) 18 19 if __name__ == "__main__": 20 c = C() 21 c.x = 10 22 print c.x 23 del c.x 24 25 26 #执行结果: 27 28 You set my x. 29 You get my x. 30 10 31 You del my x.
可见,在访问对象属性x的时候, 对应执行了property(getx, setx, delx) 所指定方法而做出一些额外的事情。另外,python提供了一个property的装饰器,上述代码 x = property([fget[, fset[, fdel[, doc]]]])可以被分化。将getter,setter,deleter方法用作装饰器的属性对象,会产生 由设置的装饰器的对应访问函数的属性的拷贝,示例如下
1 class C(object): 2 def __init__(self): 3 self._x = None 4 5 @property 6 def x(self): 7 print "You get my x." 8 return self._x 9 10 @x.setter 11 def x(self,value): 12 print "You set my x." 13 self._x = value 14 15 @x.deleter 16 def x(self): 17 print "You del my x." 18 del self._x 19 20 if __name__ == "__main__": 21 c = C() 22 c.x = 10 23 print c.x 24 del c.x
3.2 静态方法和类成员方法
3.2.1 静态方法和类成员方法对比
1)静态方法无需传入self参数,类成员方法需传入代表本类的cls参数;
静态方法和类成员方法分别在创建时被装入Staticmethod类型和Classmethod类型的对象中。静态方法的定义没有self参数,且能够被类本身直接调用。类方法在定义时需要名为cls的类似于self的参数,类成员方法可以直接用类的具体对象调用。但cls参数是自动被绑定到类的。
1 __metaclass__ = type 2 class MyClass: 3 def smeth(): 4 print 'This is a static method' 5 smeth = staticmethod(smeth) 6 7 def cmeth(cls): 8 print 'This is a class method of', cls 9 cmeth = classmethod(cmeth) 10 11 MyClass.smeth() 12 MyClass.cmeth()
3.2.2 实现静态方法和类方法的两种方式
1)Python2.3以前,用 staticmethod 和 classmethod类型对象包装实现
1 class MyClass: 2 val1 = 'Value 1' 3 def __init__(self): 4 self.val2 = 'Value 2' 5 def staticmd(): 6 print '静态方法,无法访问val1和val2' 7 smd = staticmethod(staticmd) 8 9 def classmd(cls): 10 print '类方法,类:' + str(cls) + ',val1:' + cls.val1 + ',无法访问val2的值' 11 cmd = classmethod(classmd) 12 13 #执行: 14 >>> mc = MyClass() 15 >>> mc.smd() 16 >>> mc.cmd() 17 >>> MyClass.smd() 18 >>> MyClass.cmd()
2)在Python 2.4及之后,用装饰器(decorators)实现
1 class MyClass: 2 val1 = 'Value 1' 3 def __init__(self): 4 self.val2 = 'Value 2' 5 6 @staticmethod 7 def staticmd(): 8 print '静态方法,无法访问val1和val2' 9 10 @classmethod 11 def classmd(cls): 12 print '类方法,类:' + str(cls) + ',val1:' + cls.val1 + ',无法访问val2的值'
3.3 __getattr__ __setattr__等
拦截对象的所有特性访问是可能的
魔法方法(可以对处理很多属性的方法进行再编码)
1)__getattribute__(self,name):当特性name被访问时自动被调用(只能在新式类中使用)
2)__getattr__(self,name):当特性name被访问且对象没有相应的特性时被自动调用。
3)__setattr__(self,name,value):当试图给特性name赋值时会被自动调用。
4)__delattr__(self,name):当试图删除特性name时被自动调用。
1 class Rectangle: 2 def __init__(self): 3 self.width = 0 4 self.height = 0 5 def __setattr__(self,name,value): 6 if name == 'size': 7 self.width,self.height = value 8 else: 9 self.__dict__[name] = value #特殊方法__dict__,该方法包含一个字典,字典里面是所有实例的属性,为避免__setattr__方法被再次调用(这样程序陷入死循环),__dict__方法被用来代替普通的特性赋值操作。 10 def __getattr__(self,name): 11 if name == 'size': 12 return self.width,self.height 13 else: 14 raise AttributeError
4. 迭代器
4.1 迭代器概念和优点
迭代器是访问集合元素的一种方式。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退。
对于原生支持随机访问的数据结构(如tuple、list),迭代器和经典for循环的索引访问相比并无优势,反而丢失了索引值(可以使用内建函数enumerate()找回这个索引值)。但对于无法随机访问的数据结构(比如set)而言,迭代器是唯一的访问元素的方式。
另外,迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素。迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件,或是斐波那契数列等等。
迭代器更大的功劳是提供了一个统一的访问集合的接口,只要定义了__iter__()方法对象,就可以使用迭代器访问。
4.2 使用迭代器
使用内建的工厂函数iter(iterable)可以获取迭代器对象:
>>> lst = range(2) >>> it = iter(lst) >>> it <listiterator object at 0x00BB62F0>
使用迭代器的next()方法可以访问下一个元素:
>>> it.next() 0
如果是Python 2.6+,还有内建函数next(iterator)可以完成这一功能:
>>> next(it) 1
如何判断迭代器还有更多的元素可以访问呢?Python里的迭代器并没有提供类似has_next()这样的方法。
>>> it.next() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
Python遇到这样的情况时将会抛出StopIteration异常。事实上,Python正是根据是否检查到这个异常来决定是否停止迭代的。 这种做法与迭代前手动检查是否越界相比各有优点。但Python的做法总有一些利用异常进行流程控制的嫌疑。
了解了这些情况以后,我们就能使用迭代器进行遍历了。
it = iter(lst) try: while True: val = it.next() print val except StopIteration: pass
实际上,因为迭代操作如此普遍,Python专门将关键字for用作了迭代器的语法糖。在for循环中,Python将自动调用工厂函数iter()获得迭代器,自动调用next()获取元素,还完成了检查StopIteration异常的工作。
>>> a = (1, 2, 3, 4) >>> for key in a: print key
首先Python将对关键字in后的对象调用iter函数获取迭代器,然后调用迭代器的next方法获取元素,直到抛出StopIteration异常。对迭代器调用iter函数时将返回迭代器自身,所以迭代器也可以用于for语句中,不需要特殊处理。
4.3 迭代器规则
1)到目前为止只是在for循环中对序列或字典进行迭代,但实际上也能对其他的对象进行迭代:实现__iter__方法的对象。
2)__iter__方法返回一个迭代器,所谓的迭代器就是具有next方法的对象。在调用next方法时,迭代器会返回它的下一个值。如果next方法被调用,但迭代器没有值可以返 回,就会引发一个StopIteration异常。
3)迭代规则的关键是什么?为什么不使用列表?因为列表的杀伤力太大。如果有可以一个接一个地计算值的函数,那么在使用时可能是计算一个值时获得一个值——而不是通 过列表一次性获取所有值。如果有很多值,列表就会占用太多的内存。但还有其他理由:使用迭代器更通用、更简单、更优雅。
1 class Fibs: 2 def __init__(self): 3 self.a=0 4 self.b=1 5 def __next__(self): 6 self.a,self.b = self.b,self.a+self.b 7 return self.a 8 def __iter__(self): 9 return self 10 11 12 >>> fibs = Fibs() #产生一个Fibs对象 13 >>> for f in fibs: #在for循环中使用该对象 14 if f >1000: 15 print(f) 16 break 17 18 19 1597
4.4 从迭代器得到序列
使用list构造方法显示地将迭代器转化为列表
1 >>> class TestIterator: 2 value = 0 3 def __next__(self): #此处是python3.0的版本,3.0以前的版本是用def next(self): 4 self.value +=1 5 if self.value >10: 6 raise StopIteration 7 return self.value 8 def __iter__(self): 9 return self 10 11 12 >>> ti = TestIterator() 13 >>> list(ti) 14 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
5. 生成器
生成器就是一种迭代器。生成器拥有next方法并且行为与迭代器完全相同,这意味着生成器也可以用于Python的for循环中。另外,对于生成器的特殊语法支持使得编写一个生成器比自定义一个常规的迭代器要简单不少,所以生成器也是最常用到的特性之一。
5.1 生成器函数
5.1.1 使用生成器函数定义生成器
1 >>> def get_0_1_2(): 2 ... yield 0 3 ... yield 1 4 ... yield 2 5 ... 6 >>> get_0_1_2 7 <function get_0_1_2 at 0x00B2CB70>
我们定义了一个函数get_0_1_2,并且可以查看到这确实是函数类型。但与一般的函数不同的是,get_0_1_2的函数体内使用了关键字yield,这使得get_0_1_2成为了一个生成器函数。生成器函数的特性如下:
- 调用生成器函数将返回一个生成器;
>>> generator = get_0_1_2() >>> generator <generator object get_0_1_2 at 0x00B1C7D8>
- 第一次调用生成器的next方法时,生成器才开始执行生成器函数(而不是构建生成器时),直到遇到yield时暂停执行(挂起),并且yield的参数将作为此次next方法的返回值;
>>> generator.next() 0
- 之后每次调用生成器的next方法,生成器将从上次暂停执行的位置恢复执行生成器函数,直到再次遇到yield时暂停,并且同样的,yield的参数将作为next方法的返回值;
>>> generator.next() 1 >>> generator.next() 2
- 如果当调用next方法时生成器函数结束(遇到空的return语句或是到达函数体末尾),则这次next方法的调用将抛出StopIteration异常(即for循环的终止条件);
>>> generator.next() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
- 生成器函数在每次暂停执行时,函数体内的所有变量都将被封存(freeze)在生成器中,并将在恢复执行时还原,并且类似于闭包,即使是同一个生成器函数返回的生成器,封存的变量也是互相独立的。
我们的小例子中并没有用到变量,所以这里另外定义一个生成器来展示这个特点:>>> def fibonacci(): ... a = b = 1 ... yield a ... yield b ... while True: ... a, b = b, a+b ... yield b ... >>> for num in fibonacci(): ... if num > 100: break ... print num, ... 1 1 2 3 5 8 13 21 34 55 89
5.1.2 生成器函数常见问题
- 你的例子里生成器函数都没有参数,那么生成器函数可以带参数吗?
当然可以啊亲,而且它支持函数的所有参数形式。要知道生成器函数也是函数的一种:)>>> def counter(start=0): ... while True: ... yield start ... start += 1 ...
- 既然生成器函数也是函数,那么它可以使用return输出返回值吗?
生成器函数已经有默认的返回值——生成器了,你不能再另外给一个返回值;对,即使是return None也不行。但是它可以使用空的return语句结束。如果你坚持要为它指定返回值,那么Python将在定义的位置赠送一个语法错误异常,就像这样:>>> def i_wanna_return(): ... yield None ... return None ... File "<stdin>", line 3 SyntaxError: 'return' with argument inside generator
- 好吧,那人家需要确保释放资源,需要在try...finally中yield,这会是神马情况?我在finally中还yield了一次!
Python会在真正离开try...finally时再执行finally中的代码,而这里遗憾地告诉你,暂停不算哦!所以结局你也能猜到吧!>>> def play_u(): ... try: ... yield 1 ... yield 2 ... yield 3 ... finally: ... yield 0 ... >>> for val in play_u(): print val, ... 1 2 3 0
*另外,“在带有finally子句的try块中yield”定义在PEP 342中,这意味着只有Python 2.5以上版本才支持这个语法,在Python 2.4以下版本中会得到语法错误异常。
5.1.3 生成器常见方法:
- send(value):
send是除next外另一个恢复生成器的方法。Python 2.5中,yield语句变成了yield表达式,这意味着yield现在可以有一个值,而这个值就是在生成器的send方法被调用从而恢复执行时,调用send方法的参数。
>>> def repeater(): ... n = 0 ... while True: ... n = (yield n) ... >>> r = repeater() >>> r.next() 0 >>> r.send(10) 10
*如果使用next恢复生成器,yield表达式的值将是None。 - close():
这个方法用于关闭生成器。对关闭的生成器后再次调用next或send将抛出StopIteration异常。 - throw(type, value=None, traceback=None):
这个方法用于在生成器内部(生成器的当前挂起处,或未启动时在定义处)抛出一个异常。
5.2 生成器应用
5.2.1 创建生成器(处理两层嵌套)
功能:顺序打印出列表中的数字
5.2.2 递归生成器(处理树形结构)
上述只能处理两层嵌套,每层嵌套需要加一个for循环,如何解决任意层嵌套:递归。
如下:flatten被调用时,有两种可能性:基本情况,需要递归的情况。
1) 基本情况,函数被告知展开一个元素(比如一个数字),此时,for循环会引发一个TypeError异常(因为试图对一个数字进行迭代),生成器生产一个元素。
2) 如果展开的是一个列表(或者其他可迭代对象),则需要遍历所有的子列表,并对他们调用flatten。
1 def flatten(nested): 2 2 try: 3 3 for sublist in nested: 4 4 for element in flatten(sublist): 5 5 yield element 6 6 except TypeError: 7 7 yield nested 8 8 9 9 print list(flatten([[[1],2],3,4,[5,[6,7]],8])) 10 10 11 11 print list(flatten([[1,2],[3,4],[5]])) 12 12 13 13 #python tte.py 14 14 #[1, 2, 3, 4, 5, 6, 7, 8] 15 15 #[1, 2, 3, 4, 5]
接着,如果nested是一个类似于字符串的对象(字符串,Unicode,UserString等),则它是一个序列,不会引发TypeError,若不想对其进行迭代,可以在开头添加检查语法:将传入的对象与一个字符串拼接,看是否会产生TypeError。
5.2.3 通用生成器
生成器是一个包含yield关键字的函数,当它被调用时,在函数体中的代码不会被执行,而返回一个迭代器。
每次请求一个值,就会执行生成器中的代码,直到遇到一个yield或者return语句。
yield语句意味着应该生成一个值
return语句意味着生成器要停止执行。
生成器由两部分组成:生成器的函数和生成器的迭代器
生成器的函数是用def语句定义的,包含yield的部分
生成器的迭代器是这个函数返回的部分
1 >>> def simple_generator(): 2 ... yield 1 3 ... 4 >>> simple_generator() 5 <generator object simple_generator at 0x00BBD418> 6 >>> simple_generator 7 <function simple_generator at 0x00BB2870>
6. 八皇后问题
6.1解决思路
1)状态表示
可以使用元组。每个元组中元素都指示相应行的皇后的位置(也就是列)。如果state[0]=4,表示第一行的皇后在第四列(从0开始计数)。
2)寻找冲突
已知的皇后的位置被传递给conflict函数(以状态元组的形式),然后由函数判断下一个的皇后的位置会不会又新的冲突
1 #参数nextX代表下一个皇后的水平位置(x坐标或列), nextY代表垂直位置(y坐标或行) 2 def conflict(state, nextX): 3 nextY = len(state) 4 for i in range(nextY): 5 # 如果下一个皇后和正在被考虑的当前皇后的水平距离为0(列相同)或者等于垂直距离(在一条对角线上)就返回True,否则就返回False 6 if abs(state[i]-nextX) in (0, nextY-i): ##??为啥是 nextY-i,先看基本情况的图就明白了 7 return True 8 return False
3)基本情况
即如果只剩一个皇后没有放置,那么遍历它所以可能的位置,并且返回没有冲突的位置。
1 #num皇后的总数,state是存放前面皇后的位置信息的元组 2 def queens(num, state): 3 if len(state) == num - 1: 4 for pos in range(num): 5 if not conflict(state, pos): 6 yield pos
例如,有4个皇后,前3个分别放置在1,3,0号位置,如图
print list(queens(4, (1,3,0))) #[2]
4)需要递归的情况
1 def queens(num, state): 2 if len(state) == num - 1: 3 for pos in range(num): 4 if not conflict(state, pos): 5 yield pos 6 else: 7 for pos in range(num): 8 if not conflict(state, pos): 9 for result in queens(num, state + (pos,)): 10 yield(pos,) + result 11 12 # 简化为 13 def queens(num=8, state=()): 14 for pos in range(num): 15 if not conflict(state, pos): 16 if len(state) == num - 1: 17 yield(pos,) 18 else: 19 for result in queens(num, state + (pos,)): 20 yield (pos,) + result
检测:
1 print list(queens(3)) 2 #[] 3 4 print list(queens(4)) 5 #[(1, 3, 0, 2), (2, 0, 3, 1)] 6 7 for solution in queens(8): 8 print solution 9 10 #(0, 4, 7, 5, 2, 6, 1, 3) 11 #(0, 5, 7, 2, 6, 3, 1, 4) 12 #(0, 6, 3, 5, 7, 1, 4, 2) 13 #... 14 #(7, 3, 0, 2, 5, 1, 6, 4) 15 16 17 print len(list(queens(8))) 18 #92
5)打包输出处理
random.choice从序列中获取一个随机元素。其函数原型为:random.choice(sequence)。参数sequence表示一个有序类型。这里要说明 一下:sequence在python不是一种特定的类型,而是泛指一系列的类型。list, tuple, 字符串都属于sequence。有关sequence可以查看python手册数据模型这一章。
print random.choice("学习Python")
print random.choice(["JGood", "is", "a", "handsome", "boy"])
print random.choice(("Tuple", "List", "Dict"))
1 def prettyprint(solution): 2 def line(pos, length=len(solution)): 3 return '. '*(pos) + 'X ' + '. '*(length-pos-1) 4 for pos in solution: 5 print line(pos) 6 7 import random 8 prettyprint(random.choice(list(queens(8)))) 9 #. . X . . . . . 10 #. . . . . X . . 11 #. X . . . . . . 12 #. . . . . . X . 13 #X . . . . . . . 14 #. . . X . . . . 15 #. . . . . . . X 16 #. . . . X . . . 17 18 print '' 19 20 prettyprint(random.choice(list(queens(4)))) 21 #. . X . 22 #X . . . 23 #. . . X 24 #. X . .