码迷,mamicode.com
首页 > 编程语言 > 详细

python之特殊方法、属性和迭代器

时间:2015-06-13 06:27:06      阅读:256      评论:0      收藏:0      [点我收藏+]

标签:python   方法   迭代器   

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


本文出自 “linux_oracle” 博客,请务必保留此出处http://pankuo.blog.51cto.com/8651697/1661443

python之特殊方法、属性和迭代器

标签:python   方法   迭代器   

原文地址:http://pankuo.blog.51cto.com/8651697/1661443

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!