第九章 魔法方法、特性和迭代器

第九章 魔法方法、特性和迭代器

构造函数

构造函数(constructor),它其实就是初始化方法,只是命名为__init__
构造函数不同于普通方法的地方在于,将在对象创建后自动调用它们

在Python中,创建构造函数很容易,只需将方法init的名称从普通的init改为魔法版__init__即可。

class Beyond:def __init__(self):self.band = 1999sq = Beyond()
sq.band#结果为:1999

给构造函数添加几个参数

class Beyond:def __init__(self,value = 1999):self.band = valueyy = Beyond("This is a band")
yy.band#结果为:'This is a band'

Python提供了魔法方法__del__,也称作析构函数(destructor)。这个方法在对象被销毁(作为垃圾被收集)前被调用,但鉴于你无法知道准确的调用时间,建议尽可能不要使用__del__

重写普通方法和特殊的构造函数

每个类都有一个或多个超类,并从它们那里继承行为。

对类B的实例调用方法(或访问其属性)时,如果找不到该方法(或属性),将在其超类A中查找。
类A定义了一个名为hello的方法,并被类B继承。
由于类B自己没有定义方法hello,因此对其调用方法hello时,打印的是消息"Hello, I am A."。

class A:def hello(self):print("Hello,I am A")class B(A):passa = A()
b = B()a.hello()#结果为:Hello,I am A
b.hello()#结果为:Hello,I am A

当然,类B可以重写方法hello
重写是继承机制的一个重要方面,对构造函数来说尤其重要。

class A:def hello(self):print("Hello,I am A")class B(A):def hello(self):print("Hello,I am B")b = B()
b.hello()#结果为:Hello,I am B

构造函数用于初始化新建对象的状态,而对大多数子类来说,除超类的初始化代码外,还需要有自己的初始化代码。

重写构造函数时,必须调用超类(继承的类)的构造函数,否则可能无法正确地初始化对象。
Bird类,定义了所有鸟都具备的一种基本能力:进食。鸟进食后就不再饥饿。

class Bird:def __init__(self):self.hungry = Truedef eat(self):#进食if self.hungry:print('Yes,hungry')self.hungry = False#进食完,就不饿了else:print("No,thanks!")b = Bird()
b.eat()#结果为:Yes,hungry
b.eat()#结果为:No,thanks!

子类SongBird,它新增了鸣叫功能。
SongBird是Bird的子类,继承了方法eat,但是一旦调用该方法会报异常:SongBird没有属性hungry

class SongBird(Bird):def __init__(self):self.sound = 'beyond'def sing(self):print(self.sound)sb = SongBird()
sb.sing()#结果为:beyondsb.eat()#报错!!!
'''
AttributeError  Traceback (most recent call last)
<ipython-input-34-05f67b3cf162> in <module>()
----> 1 sb.eat()<ipython-input-28-ede0d63683b8> in eat(self)3         self.hungry = True4     def eat(self):
----> 5         if self.hungry:6             print('Yes,hungry')7             self.hungry = FalseAttributeError: 'SongBird' object has no attribute 'hungry'
'''

SongBird中重写了构造函数,但新的构造函数没有包含任何初始化属性hungry的代码。要消除这种错误,SongBird的构造函数必须调用其超类(Bird)的构造函数,以确保基本的初始化得以执行。

有两种方法可以解决:调用未关联的超类构造函数,以及使用函数super
下面开始介绍这两种解决方法

调用未关联的超类构造函数

在SongBird类中,只添加了一行,其中包含代码Bird.__init__(self)
通过将这个未关联方法的self参数设置为当前实例,将使用超类的构造函数来初始化SongBird对象。这意味着将设置其属性hungry。

class Bird:def __init__(self):self.hungry = Truedef eat(self):if self.hungry:print('Yes,hungry')self.hungry = Falseelse:print("No,thanks!")class SongBird(Bird):def __init__(self):Bird.__init__(self)self.sound = 'beyond'def sing(self):print(self.sound)sb = SongBird()
sb.eat()#结果为:Yes,hungry
sb.eat()#结果为:No,thanks!

对实例调用方法时,方法的参数self将自动关联到实例(称为关联的方法)

通过类调用方法(如Bird.init),就没有实例与其相关联,可随便设置参数self,这样的方法称为未关联的。

使用函数 super

调用这个函数时,将当前类和当前实例作为参数。对其返回的对象调用方法时,调用的将是超类(而不是当前类)的方法。
在SongBird的构造函数中,可不使用Bird,而是使用super(SongBird, self)。
在Python 3中调用函数super时,可不提供任何参数(通常也应该这样做)。

class Bird:def __init__(self):self.hungry = Truedef eat(self):if self.hungry:print('Yes,hungry')self.hungry = Falseelse:print("No,thanks!")class SongBird(Bird):def __init__(self):super().__init__(self)self.sound = 'beyond'def sing(self):print(self.sound)sb = SongBird()
sb.eat()#结果为:Yes,hungry
sb.eat()#结果为:No,thanks!

相比于直接对超类调用未关联方法,使用函数super更直观
即便有多个超类,也只需调用函数super一次(条件是所有超类的构造函数也使用函数super)。
使用函数super比调用超类的未关联构造函数(或其他方法)要好得多。

函数super返回的到底是什么呢?
通常,你无需关心这个问题,只管假定它返回你所需的超类即可。
实际上,它返回的是一个super对象,这个对象将负责为你执行方法解析。
当你访问它的属性时,它将在所有的超类(以及超类的超类,等等)中查找,直到找到指定的属性或引发AttributeError异常。

元素访问

在Python中,协议通常指的是规范行为的规则
在Python中,多态仅仅基于对象的行为(而不基于祖先,如属于哪个类或其超类等)
其他的语言可能要求对象属于特定的类或实现了特定的接口,而Python通常只要求对象遵循特定的协议。

基本的序列和映射协议

序列和映射基本上是元素(item)的集合,要实现它们的基本行为(协议),不可变对象需要实现2个方法,而可变对象需要实现4个。

方法名称解释
len(self)这个方法应返回集合包含的项数,对序列来说为元素个数,对映射来说为键-值对数。如果__len__返回零(且没有实现覆盖这种行为的__nonzero__),对象在布尔上下文中将被视为假(就像空的列表、元组、字符串和字典一样)
getitem(self, key)这个方法应返回与指定键相关联的值。对序列来说,键应该是0~n-1的整数(也可以是负数),其中n为序列的长度。对映射来说,键可以是任何类型。
setitem(self, key, value)这个方法应以与键相关联的方式存储值,以便以后能够使用__getitem__来获取。当然,仅当对象可变时才需要实现这个方法。
delitem(self, key)这个方法在对对象的组成部分使用__del__语句时被调用,应删除与key相关联的值。同样,仅当对象可变(且允许其项被删除)时,才需要实现这个方法。
对于这些方法,还有一些额外的要求
对于序列,如果键为负整数,应从末尾往前数。换而言之,x[-n]应与x[len(x)-n]等效。
如果键的类型不合适(如对序列使用字符串键),可能引发TypeError异常。
对于序列,如果索引的类型是正确的,但不在允许的范围内,应引发IndexError异常。

模块collections的文档有更复杂的接口和使用的抽象基类(Sequence)

实现一个算术序列,其中任何两个相邻数字的差都相同。
第一个值是由构造函数的参数start(默认为0)指定的,而相邻值之间的差是由参数step(默认为1)指定的。
允许用户修改某些元素,这是通过将不符合规则的值保存在字典changed中实现的。
如果元素未被修改,就使用公式self.start + key * self.step来计算它的值。

'''
函数check_index(key):指定的键是否是可接受的索引?
键必须是非负整数,才是可接受的。如果不是整数,
将引发TypeError异常;如果是负数,将引发Index 
Error异常(因为这个序列的长度是无穷的)
'''
def check_index(key):#索引检查if not isinstance(key,int):raise TypeErrorif key < 0 :raise IndexErrorclass ArithmeticSequence:
#start -序列中的第一个值
#step -两个相邻值的差
#changed -一个字典,包含用户修改后的值def __init__(self,start=0,step=1):#初始化这个算术序列self.start = start#存储起始值self.step = step#存储步长值self.changed = {}#没有任何元素被修改def __getitem__(self,key):#从算术序列中获取一个元素check_index(key)try:return self.changed[key]#修改过?except KeyError:# 如果没有修改过,就计算元素的值return self.start + key * self.stepdef __setitem__(self,key,value):#修改算术序列中的元素check_index(key)self.changed[key] = value#存储修改后的值s = ArithmeticSequence(1,2)
s[4]#结果为:9s[4] = 2
s[4]#结果为:2s[5]#结果为:11#由于没有实现__del__,故禁止删除元素
del s[4]#报异常!!!
'''
AttributeError  Traceback (most recent call last)
<ipython-input-70-9cf88d1604ce> in <module>()
----> 1 del s[4]AttributeError: __delitem__
'''#这个类没有方法__len__,因为其长度是无穷的。
#如果所使用索引的类型非法,将引发TypeError异常;
#如果索引的类型正确,但不在允许的范围内(即为负数),将引发IndexError异常。
s["beyond"]#结果为:报异常!!!
'''
TypeError  Traceback (most recent call last)
<ipython-input-73-f5e041f04983> in <module>()
----> 1 s["beyond"]<ipython-input-66-2648d9225498> in __getitem__(self, key)6 7     def __getitem__(self,key):
----> 8         check_index(key)9         try:return self.changed[key]10         except KeyError:<ipython-input-55-f34d4d9f3aac> in check_index(key)1 def check_index(key):
----> 2     if not isinstance(key,int):raise TypeError3     if key < 0 :raise IndexErrorTypeError: 
'''s[-99]#结果为:报异常!!!
'''
IndexError  Traceback (most recent call last)
<ipython-input-74-8aa71bcb4b31> in <module>()
----> 1 s[-99]<ipython-input-66-2648d9225498> in __getitem__(self, key)6 7     def __getitem__(self,key):
----> 8         check_index(key)9         try:return self.changed[key]10         except KeyError:<ipython-input-55-f34d4d9f3aac> in check_index(key)1 def check_index(key):2     if not isinstance(key,int):raise TypeError
----> 3     if key < 0 :raise IndexErrorIndexError: 
'''

从 list、dict 和 str 派生

一个带访问计数器的列表。
CounterList类深深地依赖于其超类(list)的行为。

CounterList没有重写的方法(如append、extend、index等)都可直接使用。

在两个被重写的方法中,使用super来调用超类的相应方法,并添加了必要的行为:初始化属性counter(在__init__中)和更新属性counter(在__getitem__中)。

重写__getitem__并不能保证一定会捕捉用户的访问操作,因为还有其他访问列表内容的方式,如通过方法pop。

CounterList的行为在大多数方面都类似于列表,但它有一个counter属性(其初始值为0)。

每当你访问列表元素时,这个属性的值都加1。

执行加法运算cl[4] + cl[2]后,counter的值递增两次,变成了2。

class CounterList(list):def __init__(self,*args):super().__init__(*args)self.counter = 0def __getitem__(self,index):self.counter += 1return super(CounterList,self).__getitem__(index)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#结果为:0c1[4] + c1[2]#结果为:9c1.counter#结果为:2

特性

通过存取方法定义的属性通常称为特性(property)
在Python中,实际上有两种创建特定的机制,重点解释较新的那种——函数property,它只能用于新式类。

class Rectangle:def __init__(self):self.width = 0self.height = 0def set_size(self,size):self.width,self.height = sizedef get_size(self):return self.width,self.heightr = Rectangle()
r.width = 10
r.height = 5
r.get_size()#结果为:(10, 5)r.set_size((150,100))
r.width#结果为:150

get_size和set_size是假想属性size的存取方法,这个属性是一个由width和height组成的元组。(可随便将这个属性替换为更有趣的属性,如矩形的面积或其对角线长度。)

函数 property

通过调用函数property并将存取方法作为参数(获取方法在前,设置方法在后)创建了一个特性,然后将名称size关联到这个特性。

class Rectangle:def __init__(self):self.width = 0self.height = 0def set_size(self,size):self.width,self.height = sizedef get_size(self):return self.width,self.heightsize = property(get_size,set_size)r = Rectangle()
r.width = 10
r.height = 5
r.get_size()#结果为:(10, 5)r.set_size((150,100))
r.width#结果为:150

实际上,调用函数property时,还可不指定参数、指定一个参数、指定三个参数或指定四个参数。
如果没有指定任何参数,创建的特性将既不可读也不可写。
如果只指定一个参数(获取方法),创建的特性将是只读的。
第三个参数是可选的,指定用于删除属性的方法(这个方法不接受任何参数)。
第四个参数也是可选的,指定一个文档字符串。
这些参数分别名为fgetfsetfdeldoc
如果你要创建一个只可写且带文档字符串的特性,可使用它们作为关键字参数来实现。

函数property的工作原理
property其实并不是函数,而是一个类。
它的实例包含一些魔法方法,而所有的魔法都是由这些方法完成的。
这些魔法方法为__get____set____delete__,它们一道定义了所谓的描述符协议。
只要对象实现了这些方法中的任何一个,它就是一个描述符。描述符的独特之处在于其访问方式

读取属性(具体来说,是在实例中访问类中定义的属性)时,如果它关联的是一个实现了__get__的对象,将不会返回这个对象,而是调用方法__get__并将其结果返回。

静态方法和类方法

静态方法和类方法是这样创建的:将它们分别包装在staticmethodclassmethod类的对象中。
静态方法的定义中没有参数self,可直接通过来调用。
类方法的定义中包含类似于self的参数,通常被命名为cls
对于类方法,也可通过对象直接调用,但参数cls将自动关联到类。

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)MyClass.smeth()#结果为:This is a static method
MyClass.cmeth()#结果为:This is a class method of  <class '__main__.MyClass'>

在Python 2.4中,引入了一种名为装饰器的新语法,可用于像这样包装方法。(实际上,装饰器可用于包装任何可调用的对象,并且可用于方法和函数。)可指定一个或多个装饰器,为此可在方法(或函数)前面使用运算符@列出这些装饰器(指定了多个装饰器时,应用的顺序与列出的顺序相反)。
装饰器语法也可用于特性,详情请参阅有关函数property的文档。

class MyClass:@staticmethoddef smeth():print("This is a static method1")@classmethoddef cmeth(cls):print("This is a class method of1 ",cls)MyClass.smeth()#结果为:This is a static method
MyClass.cmeth()#结果为:This is a class method of  <class '__main__.MyClass'>

getattr、__setattr__等方法

方法解释
__getattribute__(self, name)在属性被访问时自动调用(只适用于新式类)
__getattr__(self, name)在属性被访问而对象没有这样的属性时自动调用。
__setattr__(self, name, value)试图给属性赋值时自动调用。
__delattr__(self, name)试图删除属性时自动调用。
'''
即便涉及的属性不是size,也将调用方法__setattr__。
因此这个方法必须考虑如下两种情形:如果涉及的属性为size,就执行与以前一样的操作;否则就使用魔法属性__dict__。
__dict__属性是一个字典,其中包含所有的实例属性。
之所以使用它而不是执行常规属性赋值,是因为旨在避免再次调用__setattr__,进而导致无限循环。
仅当没有找到指定的属性时,才会调用方法__getattr__。
这意味着如果指定的名称不是size,这个方法将引发AttributeError异常。
这在要让类能够正确地支持hasattr和getattr等内置函数时很重要。
如果指定的名称为size,就使用前一个实现中的表达式。
'''
class Rectangle:def __init__(self):self.width = 0self.height = 0def __setattr__(self,name,value):if name == 'size':self.width,self.height = valueelse:self.__dict__[name] = valuedef __getattr__(self,name):if name == 'size':return self.width,self.heightelse:raise AttributeError()

迭代器

__iter__,它是迭代器协议的基础。

迭代器协议

迭代(iterate)意味着重复多次,就像循环那样。
方法__iter__返回一个迭代器,它是包含方法__next__的对象,而调用这个方法时可不提供任何参数。
调用方法__next__时,迭代器应返回其下一个值
如果迭代器没有可供返回的值,应引发StopIteration异常。
使用内置的便利函数next,在这种情况下,next(it)与it.next()等效。

斐波那契数列

class Fibs:def __init__(self):self.a = 0self.b = 1def __next__(self):self.a , self.b = self.b , self.a + self.breturn self.adef __iter__(self):return selffibs = Fibs()
for f in fibs:if f > 1000:print(f)#结果为:1597break

通过对可迭代对象调用内置函数iter,可获得一个迭代器。

it = iter([1,2,3])
next(it)#结果为:1
next(it)#结果为:2
next(it)#结果为:3

从迭代器创建序列

使用构造函数list显式地将迭代器转换为列表。

class TestIterator:value = 0def __next__(self):self.value += 1if self.value > 10:raise StopIterationreturn self.valuedef __iter__(self):return selfti = TestIterator()
list(ti)#结果为:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

生成器

生成器它也被称为简单生成器(simple generator)。
生成器是一种使用普通函数语法定义的迭代器。

创建生成器

包含yield语句的函数都被称为生成器

生成器不是使用return返回一个值,而是可以生成多个值,每次一个。

每次使用yield生成一个值后,函数都将冻结,即在此停止执行,等待被重新唤醒。
被重新唤醒后,函数将从停止的地方开始继续执行。

def flatten(nested):for sublist in nested:for element in sublist:yield elementnested = [[1,2],[3,4],[5]]
for num in flatten(nested):print(num)#结果为:
'''
1
2
3
4
5
'''list(flatten(nested))#结果为:[1, 2, 3, 4, 5]

生成器推导(也叫生成器表达式)。其工作原理与列表推导相同,但不是创建一个列表(即不立即执行循环),而是返回一个生成器,让你能够逐步执行计算。不同于列表推导,这里使用的是圆括号。

g = ((i + 2) ** 2 for i in range(2, 27))
next(g)#结果为:16

直接在一对既有的圆括号内(如在函数调用中)使用生成器推导时,无需再添加一对圆括号。

sum(i ** 2 for i in range(10))#结果为:285

递归式生成器

上述设计的生成器只能处理两层的嵌套列表,这是使用两个for循环来实现的。
如果要处理任意层嵌套的列表,该如何办呢?
该求助于递归了。

调用flatten时,有两种可能性(处理递归时都如此):基线条件递归条件
在基线条件下,要求这个函数展开单个元素(如一个数)。
在这种情况下,for循环将引发TypeError异常(因为你试图迭代一个数),而这个生成器只生成一个元素。

如果要展开的是一个列表(或其他任何可迭代对象):遍历所有的子列表(其中有些可能并不是列表)并对它们调用flatten,然后使用另一个for循环生成展开后的子列表中的所有元素。
如果nested是字符串或类似于字符串的对象,它就属于序列,因此不会引发TypeError异常,但并不想对其进行迭代。

def flatten(nested):try:for sublist in nested:for element in flatten(sublist):yield elementexcept TypeError:yield nestedlist(flatten([[[1], 2], 3, 4, [5, [6, 7]], 8]))#结果为:[1, 2, 3, 4, 5, 6, 7, 8]

在函数flatten中,不应该对类似于字符串的对象进行迭代,主要原因有两个。
首先,你想将类似于字符串的对象视为原子值,而不是应该展开的序列。
其次,对这样的对象进行迭代会导致无穷递归,因为字符串的第一个元素是一个长度为1的字符串,而长度为1的字符串的第一个元素是字符串本身!

要处理这种问题,必须在生成器开头进行检查。
要检查对象是否类似于字符串,最简单、最快捷的方式是,尝试将对象与一个字符串拼接起来,并检查这是否会引发TypeError异常。

如果表达式nested + ''引发了TypeError异常,就忽略这种异常;
如果没有引发TypeError异常,内部try语句中的else子句将引发TypeError异常,这样将在外部的excpet子句中原封不动地生成类似于字符串的对象。

def flatten(nested):try:# 不迭代类似于字符串的对象:try:nested + ''except TypeError:passelse:raise TypeErrorfor sublist in nested:for element in flatten(sublist):yield elementexcept TypeError:yield nestedlist(flatten(['foo', ['bar', ['baz']]]))#['foo', 'bar', 'baz']

这里没有执行类型检查:没有检查nested是否是字符串,而只是检查其行为是否类似于字符串,即能否与字符串拼接

通用生成器

生成器是包含关键字yield的函数,但被调用时不会执行函数体内的代码,而是返回一个迭代器。
每次请求值时,都将执行生成器的代码,直到遇到yield或return。yield意味着应生成一个值,而return意味着生成器应停止执行(即不再生成值;仅当在生成器调用return时,才能不提供任何参数)。

换而言之,生成器由两个单独的部分组成:生成器的函数和生成器的迭代器。
生成器的函数是由def语句定义的,其中包含yield。生成器的迭代器是这个函数返回的结果。
用不太准确的话说,这两个实体通常被视为一个,通称为生成器

def simple_generator():yield isimple_generator#结果为:<function __main__.simple_generator>
simple_generator()#结果为:<generator object simple_generator at 0x000001C5C9CF2B48>

生成器的方法

在生成器开始运行后,可使用生成器和外部之间的通信渠道向它提供值。这个通信渠道包含如下两个端点。

端点名称解释
外部世界外部世界可访问生成器的方法send,这个方法类似于next,但接受一个参数(要发送的“消息”,可以是任何对象)。
生成器在挂起的生成器内部,yield可能用作表达式而不是语句。换而言之,当生成器重新运行时,yield返回一个值——通过send从外部世界发送的值。如果使用的是next,yield将返回None。

仅当生成器被挂起(即遇到第一个yield)后,使用send(而不是next)才有意义。

def repeater(value):while True:new = (yield value)if new is not None:value = newr = repeater(1999)
next(r)#结果为:1999r.send("Hello,beyond band!")#结果为:'Hello,beyond band!'

生成器还包含另外两个方法。

方法解释
throw用于在生成器中(yield表达式处)引发异常,调用时可提供一个异常类型、一个可选值和一个traceback对象。
close用于停止生成器,调用时无需提供任何参数。

模拟生成器

使用普通函数重写生成器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

八皇后问题

生成器的回溯

回溯的简单理解:
你要去参加一个很重要的会议。你不知道会议在哪里召开,但前面有两扇门,而会议室就在其中一扇门的后面。你选择进入左边那扇门后,又看到两扇门。你再次选择进入左边那扇门,但发现走错了。因此你往回走,并进入右边那扇门,但发现也走错了。因此你继续往回走到起点,现在可以尝试进入右边那扇门。

问题

将8个皇后放在棋盘上,条件是任何一个皇后都不能威胁其他皇后,即任何两个皇后都不能吃掉对方。任意两个皇后都不能处于同一行、同一列或同一斜线上(八个方向)。 怎样才能做到这一点呢?应将这些皇后放在什么地方呢?

这是一个典型的回溯问题:在棋盘的第一行尝试为第一个皇后选择一个位置,再在第二行尝试为第二个皇后选择一个位置,依次类推。在发现无法为一个皇后选择合适的位置后,回溯到前一个皇后,并尝试为它选择另一个位置。最后,要么尝试完所有的可能性,要么找到了答案。

状态表示

可简单地使用元组(或列表)来表示可能的解(或其一部分),其中每个元素表示相应行中皇后所在的位置(即列)。
如果state[0] == 3,就说明第1行的皇后放在第4列(从0开始计数)

完全可以使用列表(而不是元组)来表示状态,如果序列较小且是静态的,使用元组可能是不错的选择。

检测冲突

定义冲突:
函数conflict接受(用状态元组表示的)既有皇后的位置,并确定下一个皇后的位置是否会导致冲突。

参数nextX表示下一个皇后的水平位置(x坐标,即列),而nextY为下一个皇后的垂直位置(y坐标,即行)。

这个函数对既有的每个皇后执行简单的检查:
如果下一个皇后与当前皇后的x坐标相同或在同一条对角线上,将发生冲突,因此返回True;
如果没有发生冲突,就返回False。

abs(state[i] - nextX) in (0, nextY - i)如果下一个皇后和当前皇后的水平距离为0(在同一列)或与它们的垂直距离相等(位于一条对角线上),这个表达式就为真;否则为假。

def conflict(state, nextX):nextY = len(state)for i in range(nextY):if abs(state[i] - nextX) in (0, nextY - i):return Truereturn False

基线条件

如果只剩下最后一个皇后没有放好,就遍历所有可能的位置,并返回那些不会引发冲突的位置。参数num为皇后总数,而参数state是一个元组,包含已放好的皇后的位置。

def queens(num, state):if len(state) == num-1:for pos in range(num):if not conflict(state, pos):yield pos
#假设总共有4个皇后,而前3个皇后的位置分别为1、3和0,故还有位置2可以放置
list(queens(4, (1, 3, 0)))#结果为:[2]

递归条件

理想状态:递归调用返回当前行下面所有皇后的位置
对于递归调用,向它提供的是由当前行上面的皇后位置组成的元组。

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,)):yield(pos,) + resultlist(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)
(0, 6, 3, 5, 7, 1, 4, 2)
(0, 6, 4, 7, 1, 3, 5, 2)
(1, 3, 5, 7, 2, 0, 6, 4)
(1, 4, 6, 0, 2, 7, 5, 3)
(1, 4, 6, 3, 0, 7, 5, 2)
(1, 5, 0, 6, 3, 7, 2, 4)
(1, 5, 7, 2, 0, 3, 6, 4)
(1, 6, 2, 5, 7, 4, 0, 3)
(1, 6, 4, 7, 0, 3, 5, 2)
(1, 7, 5, 0, 2, 4, 6, 3)
(2, 0, 6, 4, 7, 1, 3, 5)
(2, 4, 1, 7, 0, 6, 3, 5)
(2, 4, 1, 7, 5, 3, 6, 0)
(2, 4, 6, 0, 3, 1, 7, 5)
(2, 4, 7, 3, 0, 6, 1, 5)
(2, 5, 1, 4, 7, 0, 6, 3)
(2, 5, 1, 6, 0, 3, 7, 4)
(2, 5, 1, 6, 4, 0, 7, 3)
(2, 5, 3, 0, 7, 4, 6, 1)
(2, 5, 3, 1, 7, 4, 6, 0)
(2, 5, 7, 0, 3, 6, 4, 1)
(2, 5, 7, 0, 4, 6, 1, 3)
(2, 5, 7, 1, 3, 0, 6, 4)
(2, 6, 1, 7, 4, 0, 3, 5)
(2, 6, 1, 7, 5, 3, 0, 4)
(2, 7, 3, 6, 0, 5, 1, 4)
(3, 0, 4, 7, 1, 6, 2, 5)
(3, 0, 4, 7, 5, 2, 6, 1)
(3, 1, 4, 7, 5, 0, 2, 6)
(3, 1, 6, 2, 5, 7, 0, 4)
(3, 1, 6, 2, 5, 7, 4, 0)
(3, 1, 6, 4, 0, 7, 5, 2)
(3, 1, 7, 4, 6, 0, 2, 5)
(3, 1, 7, 5, 0, 2, 4, 6)
(3, 5, 0, 4, 1, 7, 2, 6)
(3, 5, 7, 1, 6, 0, 2, 4)
(3, 5, 7, 2, 0, 6, 4, 1)
(3, 6, 0, 7, 4, 1, 5, 2)
(3, 6, 2, 7, 1, 4, 0, 5)
(3, 6, 4, 1, 5, 0, 2, 7)
(3, 6, 4, 2, 0, 5, 7, 1)
(3, 7, 0, 2, 5, 1, 6, 4)
(3, 7, 0, 4, 6, 1, 5, 2)
(3, 7, 4, 2, 0, 6, 1, 5)
(4, 0, 3, 5, 7, 1, 6, 2)
(4, 0, 7, 3, 1, 6, 2, 5)
(4, 0, 7, 5, 2, 6, 1, 3)
(4, 1, 3, 5, 7, 2, 0, 6)
(4, 1, 3, 6, 2, 7, 5, 0)
(4, 1, 5, 0, 6, 3, 7, 2)
(4, 1, 7, 0, 3, 6, 2, 5)
(4, 2, 0, 5, 7, 1, 3, 6)
(4, 2, 0, 6, 1, 7, 5, 3)
(4, 2, 7, 3, 6, 0, 5, 1)
(4, 6, 0, 2, 7, 5, 3, 1)
(4, 6, 0, 3, 1, 7, 5, 2)
(4, 6, 1, 3, 7, 0, 2, 5)
(4, 6, 1, 5, 2, 0, 3, 7)
(4, 6, 1, 5, 2, 0, 7, 3)
(4, 6, 3, 0, 2, 7, 5, 1)
(4, 7, 3, 0, 2, 5, 1, 6)
(4, 7, 3, 0, 6, 1, 5, 2)
(5, 0, 4, 1, 7, 2, 6, 3)
(5, 1, 6, 0, 2, 4, 7, 3)
(5, 1, 6, 0, 3, 7, 4, 2)
(5, 2, 0, 6, 4, 7, 1, 3)
(5, 2, 0, 7, 3, 1, 6, 4)
(5, 2, 0, 7, 4, 1, 3, 6)
(5, 2, 4, 6, 0, 3, 1, 7)
(5, 2, 4, 7, 0, 3, 1, 6)
(5, 2, 6, 1, 3, 7, 0, 4)
(5, 2, 6, 1, 7, 4, 0, 3)
(5, 2, 6, 3, 0, 7, 1, 4)
(5, 3, 0, 4, 7, 1, 6, 2)
(5, 3, 1, 7, 4, 6, 0, 2)
(5, 3, 6, 0, 2, 4, 1, 7)
(5, 3, 6, 0, 7, 1, 4, 2)
(5, 7, 1, 3, 0, 6, 4, 2)
(6, 0, 2, 7, 5, 3, 1, 4)
(6, 1, 3, 0, 7, 4, 2, 5)
(6, 1, 5, 2, 0, 3, 7, 4)
(6, 2, 0, 5, 7, 4, 1, 3)
(6, 2, 7, 1, 4, 0, 5, 3)
(6, 3, 1, 4, 7, 0, 2, 5)
(6, 3, 1, 7, 5, 0, 2, 4)
(6, 4, 2, 0, 5, 7, 1, 3)
(7, 1, 3, 0, 6, 4, 2, 5)
(7, 1, 4, 2, 0, 6, 3, 5)
(7, 2, 0, 5, 1, 4, 6, 3)
(7, 3, 0, 2, 5, 1, 6, 4)
'''len(list(queens(8)))#结果为:92

扫尾工作

有可能在其他地方都用不到它,故在prettyprint中创建了一个简单的辅助函数。

def prettyprint(solution):def line(pos, length=len(solution)):return '. ' * (pos) + 'X ' + '. ' * (length-pos-1)for pos in solution:print(line(pos))import random
prettyprint(random.choice(list(queens(8))))#结果为:
'''
. X . . . . . . 
. . . . . . X . 
. . X . . . . . 
. . . . . X . . 
. . . . . . . X 
. . . . X . . . 
X . . . . . . . 
. . . X . . . . 
'''

小结

概念解释
新式类和旧式类Python类的工作方式在不断变化。较新的Python 2版本有两种类,其中旧式类正在快速退出舞台。新式类是Python 2.2引入的,提供了一些额外的功能,如支持旧式类正在快速退出舞台。新式类是Python 2.2引入的,提供了一些额外的功能,如支持或设置__metaclass__。
魔法方法Python中有很多特殊方法,其名称以两个下划线开头和结尾。这些方法的功能Python中有很多特殊方法,其名称以两个下划线开头和结尾。这些方法的功能
构造函数很多面向对象语言中都有构造函数,对于你自己编写的每个类,都可能需要很多面向对象语言中都有构造函数,对于你自己编写的每个类,都可能需要
重写类可重写其超类中定义的方法(以及其他任何属性),为此只需实现这些方法即可。要调用被重写的版本,可直接通过超类调用未关联版本(旧式类),也可使用函数super来调用(新式类)。
序列和映射要创建自定义的序列或映射,必须实现序列和映射协议指定的所有方法,其中包括__getitem__和__setitem__等魔法方法。通过从list(或UserList)和dict(或UserDict)派生,可减少很多工作量。
迭代器简单地说,迭代器是包含方法__next__的对象,可用于迭代一组值。没有更多的值可供迭代时,方法__next__应引发StopIteration异常。可迭代对象包含方法__iter__,它返回一个像序列一样可用于for循环中的迭代器。通常,迭代器也是可迭代的,即包含返回迭代器本身的方法__iter__。
生成器生成器的函数是包含关键字yield的函数,它在被调用时返回一个生成器,即一种特殊的迭代器。要与活动的生成器交互,可使用方法send、throw和close。
八皇后问题八皇后问题是个著名的计算机科学问题,使用生成器可轻松地解决它。这个问题要求在棋盘上放置8个皇后,并确保任何两个皇后都不能相互攻击(一个皇后的八个方向都不能有另一个皇后的存在)。

本章介绍的新函数

函数描述
iter(obj)从可迭代对象创建一个迭代器
next(it)让迭代器前进一步并返回下一个元素
property(fget, fset, fdel, doc)返回一个特性;所有参数都是可选的
super(class, obj)返回一个超类的关联实例

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/378310.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PHP 代码 加密

PHP 代码 加密 此加密方法支持任意PHP版 代码如下: <?php function RandAbc($length""){//返回随机字符串 $str"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; return str_shuffle($str); } $filenameindex.php; $T_k1RandAbc();//随…

Python字符串| join()方法与示例

join() is an in-built method in Python and it is used to join elements of the list, string etc with the given str separator. join()是Python中的一种内置方法&#xff0c;用于通过给定的str分隔符连接列表&#xff0c;字符串等元素。 Note: Method is called with th…

C语言 链表拼接 PTA,PTA实验 链表拼接 (20point(s))

本题要求实现一个合并两个有序链表的简单函数。链表结点定义如下&#xff1a;struct ListNode {int data;struct ListNode *next;};函数接口定义&#xff1a;struct ListNode *mergelists(struct ListNode *list1, struct ListNode *list2);其中list1和list2是用户传入的两个按…

读书笔记_Effective_C++_条款十九:设计class犹如设计type

这里初看到”class”和”type”&#xff0c;感觉他们是说的是同一样东西&#xff0c;但仔细读了一下&#xff0c;两者在文中还是有区别的。class侧重于自定义的类&#xff0c;而type侧重于系统预定义的类&#xff08;像int、double、string、vector&#xff09;。设计好的class…

【TensorFlow学习笔记:神经网络优化(6讲)】

目录【1】NN复杂度【2】指数衰减学习率【3】激活函数优秀激活函数所具有的特点常见的激活函数对于初学者的建议【4】损失函数【5】缓解过拟合——正则化【6】参数优化器【1】SGD【2】SGDM(SGD基础上增加了一阶动量)【3】Adagrade(SGD基础上增加了二阶动量)【4】RMSProp(SGD基础…

kotlin 构造函数_Kotlin程序| 主要构造函数示例

kotlin 构造函数主要建设者 (Primary Constructor) A Kotlin class have Primary constructor and one or more Secondary constructor. Kotlin类具有Primary构造函数和一个或多个Secondary构造函数。 In Kotlin, Primary Constructor is the Part of Class Header. 在Kotlin中…

把SQL Server 错误日志导出为EXCEL 并发送到指定的ftp 或者 共享盘

把SQL Server 错误日志导出为EXCEL 并发送到指定的ftp 或者 共享盘 /* 2005版本 和2000 版本 sql server 错误日志结果不同。 下面是 适用于 SQL2000的 其中加入了 自己编写的一个ftp小程序 用来上传 相关日志状况*/IF object_id(tempdb..#error_log) IS NOT NULLD…

c语言软件幻化,python字符串处理

字符串字符串&#xff1a;不可变有序序列&#xff0c;在python可使用 "abc" , """abc""" ,abc 的形式表示&#xff0c;属于一种字面常量&#xff0c;python3中字符均属于Unicode编码。字符串可以被迭代&#xff0c;遍历&#xff0c;切…

第十章 开箱即用

第十章 开箱即用 “开箱即用”&#xff08;batteries included&#xff09;最初是由Frank Stajano提出的&#xff0c;指的是Python丰富的标准库。 模块 使用import将函数从外部模块导入到程序中。 import math math.sin(0)#结果为&#xff1a;0.0模块就是程序 在文件夹中创…

博客换肤

一直以来觉得博客园里的主题不是特别好&#xff0c;很多模版多多少少都有些美中不足&#xff0c;于是今天抽了点时间把主题改改&#xff0c;哈。幸亏博客园支持自定义样式。 时间有限就先改到这里&#xff0c;今后有时间在进一步完美&#xff0c;呵呵。转载于:https://www.cnbl…

duration java_Java Duration类| 带示例的get()方法

duration java持续时间类get()方法 (Duration Class get() method) get() method is available in java.time package. get()方法在java.time包中可用。 get() method is used to return the value for the given unit. get()方法用于返回给定单位的值。 get() method is a non…

Openmv通过串口接收数据、发送数据与stm32通信

博主联系方式: QQ:1540984562 QQ交流群:892023501 群里会有往届的smarters和电赛选手,群里也会不时分享一些有用的资料,有问题可以在群里多问问。 目录 参考接线星瞳教程openmv传送数据STM32解码程序参考 接线 星瞳教程

c语言尹宝林答案,c程序设计导引 尹宝林

《C程序设计导引》特别适合作为计算机和非计算机专业学生学习高级语言程序设计的教材&#xff0c;也可供计算机等级考试者和其他各类学习者使用参考。17.40定价&#xff1a;44.75(3.89折)/2013-05-01《大学计算机优秀教材系列&#xff1a;C程序设计导引》是一本讲解C程序设计的…

从程序员到项目经理(15):项目管理三大目标

项目管理的三大目标即时间、成本和质量&#xff0c;实际是告诉项目经理应重点关注什么因素&#xff0c;项目控制应该做什么工作。三大目标虽然简单&#xff0c;但如果能将其真正贯彻到自己的行动中&#xff0c;那么对项目计划制定、过程控制等工作&#xff0c;均能起到引导作用…

第十一章 文件

第十一章 文件 打开文件 当前目录中有一个名为beyond.txt的文本文件&#xff0c;打开该文件 调用open时&#xff0c;原本可以不指定模式&#xff0c;因为其默认值就是’r’。 import io f open(beyond.txt)文件模式 值描述‘r’读取模式&#xff08;默认值&#xff09;‘w…

iterator与iterable

用Iterator模式实现遍历集合Iterator模式是用于遍历集合类的标准访问方法。它可以把访问逻辑从不同类型的集合类中抽象出来&#xff0c;从而避免向客户端暴露集合的内部结构。例如&#xff0c;如果没有使用Iterator&#xff0c;遍历一个数组的方法是使用索引&#xff1a; for(i…

Android中的Touch事件处理流程

1、当触摸屏幕时&#xff0c;最先执行的是Activity中的 publicbooleandispatchTouchEvent (MotionEvent ev) {if(ev.getAction() MotionEvent. ACTION_DOWN) {onUserInteraction();}if(getWindow().superDispatchTouchEvent(ev)) {returntrue;}returnonTouchEvent(ev);}如果某…

【TensorFlow学习笔记:神经网络八股】(实现MNIST数据集手写数字识别分类以及FASHION数据集衣裤识别分类)

课程来源&#xff1a;人工智能实践:Tensorflow笔记2 文章目录前言一、搭建网络八股sequential1.函数介绍2.6步法实现鸢尾花分类二、搭建网络八股class1.创建自己的神经网络模板&#xff1a;2.调用自己创建的model对象三、MNIST数据集1.用sequential搭建网络实现手写数字识别2.用…

c语言 在执行区域没有空格,C语言上机操作指导之TurboC.doc

C语言上机操作指导之 -------- Turbo C程序设计是实践性很强的过程&#xff0c;任何程序都必须在计算机上运行&#xff0c;以检验程序的正确与否。因此在学习程序设计中&#xff0c;一定要重视上机实践环节&#xff0c;通过上机可以加深理解 C语言的有关概念&#xff0c;以巩固…

java 根据类名示例化类_Java即时类| from()方法与示例

java 根据类名示例化类即时类from()方法 (Instant Class from() method) from() method is available in java.time package. from()方法在java.time包中可用。 from() method is used to return a copy of the Instant from the given TemporalAccessor object. from()方法用于…