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

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

構造函數

構造函數(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.pswp.cn/news/378310.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/378310.shtml
英文地址,請注明出處:http://en.pswp.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()方法用于…