9.1 准备工作
class NewStyle(object)
more_code_here
class OldStyle:
more_code_here
在这两个类中,NewStyle是新式的类,OldStyle是旧式的类。如果文件以__metaclass__=type开始,那么两个类都是新式类。
除此之外,还可以在自己的类的作用域中对__metaclass__变量赋值。这样只会为这个类设定元类。元类是其他类的类-----这是个更高级的主题。
9.2 构造方法
当一个对象被创建后,会立即调用构造方法。
>>>f = FooBar()
>>>f.init()
构造方法能让它简化成如下形式:
>>>f = FooBar()
在python中创建一个构造方法很容易。只要把init方法的名字从简单的init修改为魔法版本__init__即可:
class FooBar:
def __init__(self):
self.somevar = 42
>>>f = FooBar()
>>>f.somevar
42
class FooBar():
def __init__(self,value=42)
self.somevar = value
>>>f = FooBar(‘This is a constructor argument‘)
>>>f.somevar
‘This is a constructor argument‘
python中有一个魔法方法叫做__del__,就是析构方法。它在对象就要被垃圾回收之前调用,但发生调用的具体时间是不可知的,所以建议读者尽力避免使用__del__函数。
9.2.1 重写一般方法和特殊的构造方法
如果一个方法在B类的一个实例中被调用,但在B类中没有找到该方法,那么就会去它的超类A里面找。
class A:
def hello(self):
print "hello,I‘m A."
class B(A):
pass
A类 定义了一个叫做hello的方法,被B类继承。下面是一个说明类是如何工作的例子:
>>>a = A()
>>>b = B()
>>>a.hello()
hello,I‘m A.
>>>b.hello()
hello,I‘m A
因为B类没有定义自己的hello方法,所以当hello被调用时,原始的信息就被打印出来。
在子类中增加功能的最基本的方式就是增加方法。但是也可以重写一些超类的方法来自定义继承的行为。B类也能重写这个方法。比如下面的例子中B类的定义被修改了。
class B(A):
def hello(self):
print "hello,I‘m B."
使用这个定义,b.hello()能产生一个不同的结果。
>>>b = B()
>>>b.hello()
hello,I‘m B.
虽然重写的机制对于所有方法来说都是一样的,但是当处理构造方法比重写普通方法时,更可能遇到特别的问题:如果一个类的构造方法被重写,那么就需要调用超类的构造方法,否则对象可能不会被正确地初始化。
class Bird:
def __init__(self):
self.hungry = True
def eat(self):
if self.hungry:
print ‘Aaaah...‘
self.hungry = False
else:
print ‘No,thanks!‘
这个类定义所有的鸟都具有的一些最基本的能力:吃,用法示例;
>>>b = Bird()
>>>b.eat()
Aaaah...
>>>b.eat()
No,thanks!
就像能在这个例子中看到的,鸟吃过了以后,它就不再饥饿。为子类SongBird,它添加了唱歌的行为。
class SongBird(Bird):
def __init__(self):
self.sound = ‘Squawk!‘
def sing(self):
print self.sound
SongBird类和Bird类一样容易使用:
>>>sb = SongBird()
>>>sb.sing()
Squawk!
因为SongBird是Bird的一个子类,它继承了eat方法,但如果调用eat方法,就会产生一个问题:
>>>sb.eat()
报错
异常很清楚地说明了错误:SongBird没有hungry特性。原因是这样的:在SongBird中,构造方法被重写,但新的构造方法没有任何关于初始化hungry特性的代码。为了达到预期的效果,SongBird的构造方法必须调用其超类Bird的构造方法来确保进行基本的初始化。有两种方法能达到这个目的:调用超类构造方法的未绑定版本,或者使用super函数。
9.2.2 调用未绑定的超类构造方法
9.2.3 使用super函数
只能在新式类使用super函数。当前的类和对象可以作为super函数的参数使用,调用函数返回的对象的任何方法都是调用超类的方法,而不是当前类的方法。那么就可以不用再SongBird的构造方法中使用Bird,而直接使用super(SongBird,self)。除此之外__init__方法能以一个普通的绑定方式被调用。
在python3.0 super函数可以不用任何参数进行调用。
下面的例子是对bird例子的更新。
__metaclass__ = type
class Bird:
def __init__(self):
self.hungry = True
def eat(self):
if self.hungry:
print ‘Aaaah...‘
self.hungry = False
else:
print ‘No,thanks!‘
class SongBird(Bird):
def __init__(self):
super(SongBird,self).__init__()
self.sound = ‘Squawk!‘
def sing(self):
print self.sound
>>>sb = SongBird()
>>>sb.sing()
Squawk!
>>>sb.eat()
Aaaah...
>>>sb.eat()
No,thanks
9.3 成员访问
规则:它是管理某种形式的行为的规则
9.3.1 基本的序列和映射规则
序列和映射是对象的集合。为了实现它们基本的行为(规则),如果对象是不可变的,那么就需要使用两个魔法方法,如果是可变的则需要使用4个。
1.__len__(self):这个方法应该返回集合中所含项目的数量。对于序列来说,这就是元素的个数;对于映射来说,则是键-值对得数量。如果__len__返回0(并且没有实现重写该行为的__nonzero__),对象会被当作一个布尔变量中的假值(空的列表,元组,字符串和字典一样)进行处理。
2.__getitem__(self,key):这个方法返回与所给键对应的值。对于一个序列,键应该是一个0~n-1的整数(或者像后面所说的负数),n是序列的长度;对于映射来说,可以使用任何种类的键。
3.__setitem__(self,key,value):这个方法应该按一定的方式存储和key相关的value,该值随可使用__getitem__来获取。当然,只能为可以修改的对象定义这个方法。
4.__delitem__(self,key):这个方法对一部分对象使用del语句时被调用,同时必须删除和元素相关的建。这个方法也是为可修改的对象定义的(并不是删除全部的对象,而只删除一些需要移除的元素)。
对这些方法的附加要求:
1.对于一个序列来说,如果键是负整数,那么要从末尾开始计数。换句话说就是x[-n]和x[len(x)-n]是一样的;
2.如果键是不适合的类型(例如,对序列使用字符串作为键),会引发一个TypeError异常;
3.如果序列的索引是正确的类型,但超出了范围,应该引发一个IndexError异常。
创建一个无穷序列:
def checkIndex(key):
if not isinstance(key),(int,long): raise TypeError
if key<0: raise IndexError
#键应该是一个非负整数
class ArithmeticSequence:
def __init__(self,start=0,step=1):
self.start = start #序列中第一个值
self.step = step #步长----两个相邻值之间的差别
self.changed = {} #改变----用户修改的值的字典
#初始化算术序列
def __getitem__(self,key):
Get an item from the arithmetic sequence.
checkIndex(key)
try: return self.changed[key] #修改了么
except KeyError: #否则。。。。
return self.start + key*self.step #。。。。。计算值
def __setitem__(self,key,value):
#修改算术序列中的一个项
checkIndex(key)
self.changed[key] = value #保存更改后的值
这里实现的是一个算术序列,该序列中的每个元素都比它前面的元素大一个常数。第一个值是由构造方法参数start(默认为0)给出的,而值与值之间的步长是由step设定的(默认为1)。用户能通过名为changed的方法将特例规则保存在字典中,从而修改一些元素的值,如果元素没有被修改,那就计算self start+key* step的值。
>>>s = ArithmeticSequence(1,2)
>>>s[4]
9
>>>s[4] = 2
>>>s[4]
2
>>>s[5]
11
注意,没有实现__del__方法的原因是我希望删除元素是非法的:
>>>del s[4]
报错
这个类没有__len__方法,因为它是无限长的。
如果使用了一个非法类型的索引,就会引发TypeError异常,如果索引的类型是正确的但超出了范围,则会引发IndexError异常:
>>>s["four"]
报错
>>>s[-42]
报错
索引检查是通过用户自定义的checkIndex函数实现的。
9.3.2 子类化列表,字典和字符串
如果希望实现一个和内建列表行为相似的序列,可以使用子类list。
当子类化一个内建类型----比如list的时候,也就间接的将object子类化了,因此类就自动成为新式类,这就意味着可以使用像super函数这样的特性了。
带有访问计数的列表
class CounterList(list):
def __init__(self,*args):
super(CounterList,self).__init__(*args)
self.counter = 0
def __getitem__(self,index):
self.counter += 1
return super(CounterList,self).__getitem__(index)
CounterList类严重依赖它的子类化超类的行为。CounterList类没有重写任何的方法(和append,extend,index一样)都能被直接使用。在两个被重写的方法中,super方法被用来调用相应的超类的方法,只在__init__中添加了所需的初始化counter特性的行为,并在__getitem__中更新了counter特性。
CounterList如何使用的例子
>>>c1 = CounterList(range(10))
>>>c1
[0,1,2,3,4,5,6,7,8,9]
>>>c1.reverse()
>>>c1
[9,8,7,6,5,4,3,2,1,0]
>>>del c1[3:6]
>>>c1
[9,8,7,3,2,1,0]
>>>c1.counter
0
>>>c1[4] + c1[2]
9
>>>c1.counter
2
正如看到的,CounterList在很多方面和列表的作用一样,但它有一个counter特性,每次列表元素被访问时,它都会自增,所以在执行家法c1[4] + c1[2],这个值自增两次,变为2.
9.5 属性
访问器是一个简单的方法,它能够使用getHeight、setHeight这样的名字来得到或者重绑定一些特性。
class Rectangle:
def __init__(self):
self.width = 0
self.height = 0
def setSize(self,size):
self.width,self.height = size
def getSize(self):
return self.width,self.height
>>>r = Rectang()
>>>r.width = 10
>>>r.height = 5
>>>r.getSize()
(10,5)
>>>r.setSize((150,100))
>>>r.width
150
python能隐藏访问器方法,让所有特性看起来一样。这些通过访问器定义的特性被称为属性。
9.5.1 property函数
property函数的使用很简单。如果已经编写了一个像上节的Rectangle那样的类,那么只要增加一行代码(子类化object,或者使用__metaclass__=type语句):
__metaclass__=type
class Rectangle:
def __init__(self):
self.width =0
self.height = 0
def setSize(self,size):
self.width,self.height = size
def getSize(self):
return self.width,self.height
size = property(getSize,setSize)
在这个新版的Rectangle中,property函数创建了一个属性,其中访问器函数被用作参数(先是取值,然后是赋值),这个属性命为size。这样一来就不再需要担心是怎么实现的了,可以用同样的方式处理width、height和size。
>>>r = Rectangle()
>>>r.width = 10
>>>r.height = 5
>>>r.size
(10,5)
>>>r.size = 150,100
>>>r.width
150
很明显,size特性仍然取决于getSize和setSize中的计算。但它们看起来就像普通的属性一样。
如果属性的行为很奇怪,那么要确保你所使用的类为新式类;如果不是的话,虽然属性的取值部分还是可以工作,但赋值部分就不一定了。
实际上,property函数可以用0,1,2,3或者4个参数来调用。如果没有参数,产生的属性既不可读,也不可写。如果只使用一个参数调用(一个取值方法),产生的属性是只读的,第三个参数是可选是一个用于删除特性的方法。第四个参数可选是一个文档字符串。property的4个参数分别被叫做fget、fset、fdel和doc--,如果想要一个属性是只写的,并且有一个文档字符串,能使用它们作为关键字参数。
9.5.2 静态方法和类成员方法
静态方法和类成员方法分别在创建时分别被装入Staticmethod类型和Classmethod类型的对象中。静态方法的定义没有self参数,且能够被类本身直接调用。类方法在定义时需要名为cls的类似于self的参数,类成员方法可以直接用类的具体对象调用。但cls参数是自动被绑定到类的:
__metaclass__ = type
class MyClass:
def smeth():
print ‘This is a static method‘
smeth = staticmethod(smeth)
def cmeth(cls):
print ‘This is a class method of‘,cls
cmeth = classmethod(cmeth)
手动包装和替换方法的技术看起来有点单调。python 2.4中,为这样的包装方法引入了一个叫做装饰器的新语法。使用@操作符,在方法的上方将装饰器列出,从而指定一个或者更多的装饰器。
__metaclass__ = type
class Myclass:
@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‘>
静态方法和类成员方法在python中并不是向来很重要,主要的原因时大部分情况不可以使用函数或者绑定方法代替。在早期的版本中没有得到支持也是一个原因。但即使看不到两者在当前代码中的大量应用,也不要忽视静态方法和类成员方法的应用。
9.5.3 __getattr__ setattr__和它的朋友们
拦截对象的所有特性访问时可能的,这样可以用旧式类实现属性。为了在访问特性的时候可以执行代码,必须使用一些魔法方法。
1.__getattribute__(self,name):当特性name被访问时自动调用。
2.__getattr__(self,name):当特性name被访问且对象没有相应的特性时被自动调用。
3.__setattr__(self,name,value):当试图给特性name赋值时会被自动调用。
4.__delattr__(self,name):当试图删除特性name时被自动调用。
尽管和使用property函数相比有点复杂(而且在某些方面效率更低),但这些方法是很强大的,因为可以对处理很多属性的方法进行再编码。
下面还是Rectangle的例子,但这次使用的是特殊方法:
class Rectangle:
def __init__(self):
self.width = 0
self.heigth = 0
def __setattr__(self,name,value):
if name == ‘size‘:
self.width , self.heigth = value
else:
self.__dict_[name] = value
def __getattr__(self,name)
if name == ‘size‘:
return self.width , self.heigth
else:
raise AttributeError
这个版本的类需要注意增加的管理细节。当思考这个例子时,下面的亮点应该引起读者的重视:
1.__setattr__方法在所涉及到的特性不是size时也会调用。因为,这个方法必须把两方面都考虑进去:如果属性是size,那么就像前面那样执行操作,否则就要使用特殊方法__dict__,该特殊方法包含一个字典,字典里面所有实例的属性。为了避免__setarrt__方法被再次使用,__dict__方法被用来代替普通的特性赋值操作。
2.__getattr__方法只在普通的特性没有被找到的时候调用,这就是说如果给定的名字不是size,这个特性不存在,这个方法会引发一个AttributeError异常。如果希望类和hasattr或者是getattr这样的内建函数一起正确地工作,__getattr__方法就很重要。如果使用的时size属性,那么就会使用在前面的实现中找到的表达式。
就像死循环陷阱和__setattr__有关系一样,还有一个陷阱和__getattribute__有关系。因为__getattribute__拦截所有特性的访问,也拦截对__dict__的访问!访问__getattribute__中与Self相关的特性时,使用超类的__getattribute__方法是唯一安全的途径。
9.6 迭代器
9.6.1 迭代器规则
迭代的意思是重复做一些事很多次------就像在循环中做得那样。到现在为止只是在for循环中对序列和字典进行迭代,但实际上也能对其他的对象进行迭代:实现__iter__方法的对象。
__iter__方法返回一个迭代器,所谓的迭代器就是具有next方法(这个方法在调用时不需要任何参数)的对象。在调用next方法时,迭代器会返回它的下一个值。如果next方法被调用,但迭代器没有值可以返回,就会引发一个StopIteration异常。
迭代器规则在python 3.0 中有一些变化,在新的规则中,迭代器对象应该实现__next__方法,而不是next,而新的内建函数next可以用于访问这个方法。换句话说,next(it)等同于3.0之前版本中的it.next()
这里的列表是一个斐波那契数列。使用迭代器如下:
class Fibs:
def __init__(self):
self.a = 0
self.b = 1
def next(self):
self.a , self.b = self.b , self.a+self.b
return self.a
def __iter__(self):
return self
注意,迭代器实现了__iter__方法,这个方法实际上返回迭代器本身。在很多情况下,__iter__会放到其他的会在for循环中使用的对象中。这样一来,程序就能返回所需的迭代器。此外,推荐使迭代器实现它自己的__iter__方法,然后就能直接在for循环中使用迭代其本身。
正式的说法是,一个实现了__iter__方法的对象是可迭代的,一个实现了next方法的对象则是迭代器。
首先 产生一个Fibs对象:
>>>fibs = Fibs()
可在for循环中使用该对象----比如去查找在斐波那契数列中比1000大的数中的最小的数:
>>>for f in fibs:
if f > 1000:
print f
break
...
159
因为设置了break,所以循环在这里停止了,否则循环会一直继续下去。
内建函数iter可以从可迭代的对象中获得迭代器。
>>>it = iter([1,2,3])
>>>it.next()
1
>>>it.next()
2
9.6.2 从迭代器得到序列
除了在迭代器和可迭代对象上进行迭代外,还能把它们转换成序列。在大部分能使用序列的情况下,能使用迭代器替换。关于这个的一个很有用的例子是使用list构造方法显式的将迭代器转化为列表。
>>>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]
9.7 生成器
生成器是一种用普通的函数语法定义的迭代器.
nested = [[1,2],[3,4],[5]]
def flatten(nested):
for sublist in nested:
for element in sublist:
yield element
首先迭代提供的嵌套列表中的所有子列表,然后按顺序迭代子列表中的元素.如果最后一行是print element的话,那么就容易理解力.
任何包含yield语句的函数称为生成器.除了名字不同以外,它的行为和普通的函数也有很大的差别,这就在于它不是像return那样返回值,而是每次产生多个值.每次产生一个值,函数就会被冻结:即函数停在那点等待被激活.函数被激活后就从停止的那点开始执行.
通过在生成器上迭代来使用所有的值.
>>>nested = [[1,2],[3,4],[5]]
>>>for num in flatten(nested):
print num
...
1
2
3
4
5
or
>>>list(flatten(nested))
[1,2,3,4,5]
循环生成器
生成器推导式和列表推导式的工作方式类似,只不过返回的不是列表而是生成器,所返回的生成器允许你像下面这样一步一步地进行计算:
>>>g = ((i+2)**2 for i in range(2,27))
>>>g.next()
16
和列表推导式不同的就是普通圆括号的使用方式,在这样简单的例子中,还是推荐大家使用列表推导式.
生成器推导式可以在当前的圆括号直接使用,例如在函数调用中,不用增加另外一对圆括号,换句话说,可以像下面这样编写代码:
sum(i**2 for i in range(10))
9.7.2 递归生成器
def flatten(nested)
try:
for sublist in nested:
for element in flatten(sublist:)
yield element
except TypeError:
yield nested
当flatten被调用时,有两种可能性:基本情况和需要递归的情况。在基本的情况中,函数被告知展开一个元素,这种情况下,for循环会引发一个TypeError异常,生成器会产生一个元素。
如果展开的是一个列表,那么就要进行特殊处理。程序必须遍历所有的子列表,并对它们调用flatten。然后使用另一个for循环来产生被展开的子列表中的所有元素。
>>>list(flatten([[[1],2],3,4,[5,[6,7]],8]))
[1,2,3,4,5,6,7,8]
这么做只有一个问题:如果nested是一个类似于字符串的对象,那么它就是一个序列,不会引发TypeError,但是你不想对这样的对象进行迭代。
不应该在flatten函数中对类似于字符串的对象进行迭代,出于两个主要的原因。首先,需要实现的是将类似于字符串的对象当成原子值,而不是当成应被展开的序列。其次,对它们进行迭代实际上会导致无穷递归,因为一个字符串的第一个元素是另一个长度为1的字符串,而长度为1的字符串的第一个元素就是字符串本身。
为了处理这种情况,则必须在生成器的开始处添加一个检查语句。试着将传入的对象和一个字符串拼接,看看会不会出现TypeError,这是检查一个对象是不是似类于字符串的最简单、最快速的方法。下面是加入了检查语句的生成器:
def flatten(nested):
try:
try: nested+ ‘‘
except TypeError:pass
else:raise TypeError
for sublist in nested:
for element in flatten(sublist):
yield element
except TypeError:
yield nested
如果表达式nested+ 引发了一个TypeError,它就会被忽略。然而如果没有引发TypeError,那么内层try语句中的else子句就会引发一个它自己的TypeError异常。这就会按照原来的样子生成类似于字符串的对象。
>>>list(flatten({‘foo‘,[‘bar‘,[‘baz‘]]]))
[‘foo‘,‘bar‘,‘baz‘]
上面的代码没有执行类型检查.这里没有测试nested是否是一个字符串,而只是检查nested的行为是不是像一个字符串.
9.7.3 通用生成器
生成器是一个包含yield关键字的函数.当它被调用时,在函数体中的代码不会被执行,而会返回一个迭代器.每次请求一个值,就会执行生成器中德代码,直到遇到一个yield或者return语句.yield语句意味着应该生成一个值.return语句意味着生成器要停止执行.换句话说,生成器是两部分组成:生成器的函数和生成器的迭代器.生成器的函数是def语句定义的,包含yield的部分,生成器的迭代器是这个函数返回的部分.按一种不是很准确的说法,两个实体经常被当作一个,合起来叫做生成器.
>>>def simple_generator():
yield 1
>>>simple_generator
<function simple_generator at 153b44>
>>>simple_generator()
<generator object at 1510b0>
生成器的函数返回的迭代器可以像其他的迭代器那样使用.
9.7.4 生成器方法
生成器的新属性是在开始运行后卫生成器提供值得能力.表现为生成器和"外部世界"进行交流的渠道,要注意下面两点.
1.外部作用域访问生成器的send方法,就像访问next方法一样,只不过前者使用一个参数
2.在内部则刮起生成器,yield现在作为表达式而不是语句使用,换句话说,当生成器重新运行的时候,yield方法返回一个值,也就是外部通过send方法发送的值.如果next方法被使用,那么yield方法返回None.
注意,使用send方法只有在生成器挂起之后才有意义,如果在此之前需要给生成器提供更多信息,那么只需使用生成器函数的参数.
def repeater(value):
while True:
new = (yield value)
if new is not None: value = new
使用方法如下:
r = repeater(42)
r.next()
42
r.send("Hello,world!")
"Hello,world!"
注意看yield表达式周围的括号的使用.虽然并未严格要求,但在使用返回值得时候,安全起见还是要闭合yield表达式.
生成器还有其他两种方法:
1.throw方法用于在生成器内引发一个异常
2.close方法用于停止生成器
close方法也是建立在异常的基础上的.他在yield运行处引发一个GeneratorExit异常,所以如果需要在生成器内进行代码清理的话,则可以将yield语句放在try/finally语句中.如果需要的话,还可以捕捉GeneratorExit异常,但随后必需将其重新引发,引发另外一个异常或者直接返回.试着在生成器的close方法被调用后再通过生成器生成一个值则会导致RuntimeError异常.
9.7.5 模拟生成器
如何使用普通的函数模拟生成器。
先从生成器的代码开始。首先将下面语句放在函数体的开始处:
result = []
如果代码已经使用了result这个名字,那么应该用其他名字代替,然后将下面这种形式的代码
yield some_expression
用下面的语句替换:
result.append(some_expression)
最后,在函数的末尾,添加下面这条语句:
return result
尽管这个版本可能不适用于所有生成器,但对大多数生成器来说是可行的。
flatten生成器用普通的函数重写的版本
def flatten(nested):
result = []
try:
try: nested + ‘‘
except TypeError:pass
else:raise TypeError
for sublist in nested:
for element in flatten(sublist):
result.append(element)
except TypeError:
result.append(nested)
return result
9.8 八皇后问题
9.8.1 生成器和回溯
生成器是逐渐产生结果的复杂递归算法的理想实现工具。
def conflict(state,nextX):
nextY = len(state)
for i in range(nextY):
if abs(state[i]-nextX) in (0,nextY-i):
return True
return False
参数nextX代表下一个皇后的水平位置(x坐标或列),nextY代表垂直位置(y坐标或行)。这个函数对前面的每个皇后的位置做一个简单的检查,如果下一个皇后和前面的皇后有同样的水平位置,或者是在一条对角线上,就会发生冲突,接着返回True。如果没有这样的冲突发生,那么返回False,不太容易理解的是下面的表达式:
abs(state[i]-nextX) in (0,nextY-i):
如果下一个皇后和正在考虑的前一个皇后的水平距离为0或者等于垂直距离就返回True,否则就返回False。
9.8.5 基本情况
从基本的情况开始:最后一个皇后。你想让它做什么?假设你想找出所有可能的解决方案;
这样一来,它能根据其他的皇后位置生成它自己能占据的所有位置。能把这样的情况直接描绘出。
def queens(num,state):
if len(state) == num-1:
for pos in range(num):
if not conflict(state,pos):
yield pos
用人类的语言来描述,它的意思是:如果只剩一个皇后没有位置,那么遍历它所有的可能的位置,并且返回没有冲突发生的位置。num参数是皇后的总数。state参数是存放前面皇后的位置信息的元组。假设有4个皇后,前3个分别被放置在1,3,0号位置。
>>>list(queens(4,(1,3,0)))
[2]
9.8.6 需要递归的情况
...
else:
for pos in range(num):
if not conflict(state,pos):
for result in queens(num,state + (pos,))
yield(pos,)+result
for pos和if not conflice部分和前面的代码相同。添加一些默认的参数:
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,)):
yiled(pos) + result
生成器queens能给出所有的解决方案
>>>list(queens(3))
[]
>>>list(queens(4))
[(1,3,0,2),(2,0,3,1)]
>>>for solution in queens(8):
print solution
...
(0,4,7,5,2,6,1,3)
(0,5,7,2,6,3,1,4)
...
如果用8个皇后做参数来运行queens。
>>>len(list(queens(8)))
92
9.8.7 打包
def prettyprint(solution):
def line(pos,length=len(solution));
return ‘. ‘ * pos + ‘X ‘ + ‘. ‘ * (length-pos-1)
for pos in solution:
print line(pos)
注意prettyprint中创建了一个小的助手函数。之所以将其放在prettyprint内,是因为我们假设在外面的任何地方都不会用到它。下面打印出一个令我满意的随机解决方案。可以看到该方案是正确的。
>>>import random
>>>prettyprint(random.choice(list(queens(8))))