9.1 構(gòu)造函數(shù)
我們要介紹的第一個(gè)魔法方法是構(gòu)造函數(shù)。你可能從未聽(tīng)說(shuō)過(guò)構(gòu)造函數(shù)(constructor),它其實(shí)就是本書前面一些示例中使用的初始化方法,只是命名為init。然而,構(gòu)造函數(shù)不同于普通方法的地方在于,將在對(duì)象創(chuàng)建后自動(dòng)調(diào)用它們。因此,無(wú)需采用本書前面一直使用的做法:
>>> f = FooBar()
>>> f.init()
構(gòu)造函數(shù)讓你只需像下面這樣做:
>>> f = FooBar()
在Python中,創(chuàng)建構(gòu)造函數(shù)很容易,只需將方法init的名稱從普通的init改為魔法版init即可。
class FooBar:
def __init__(self):
self.somevar = 42
>>> f = FooBar()
>>> f.somevar
42
到目前為止一切順利。但你可能會(huì)問(wèn),如果給構(gòu)造函數(shù)添加幾個(gè)參數(shù),結(jié)果將如何呢?請(qǐng)看下面的代碼:
class FooBar:
def __init__(self, value=42):
self.somevar = value
9.1.1 重寫普通方法和特殊的構(gòu)造函數(shù)
第7章介紹了繼承。每個(gè)類都有一個(gè)或多個(gè)超類,并從它們那里繼承行為。對(duì)類B的實(shí)例調(diào)用方法(或訪問(wèn)其屬性)時(shí),如果找不到該方法(或?qū)傩裕?,將在其超類A中查找。請(qǐng)看下面兩個(gè)類:
class A:
def hello(self):
print("Hello, I'm A.")
class B(A):
pass
類A定義了一個(gè)名為hello的方法,并被類B繼承。下面的示例演示了這些類是如何工作的:
>>> a = A()
>>> b = B()
>>> a.hello()
Hello, I'm A.
>>> b.hello()
Hello, I'm A.
構(gòu)造函數(shù)用于初始化新建對(duì)象的狀態(tài),而對(duì)大多數(shù)子類來(lái)說(shuō),除超類的初始化代碼外,還需要有自己的初始化代碼。雖然所有方法的重寫機(jī)制都相同,但與重寫普通方法相比,重寫構(gòu)造函數(shù)時(shí)更有可能遇到一個(gè)特別的問(wèn)題:重寫構(gòu)造函數(shù)時(shí),必須調(diào)用超類(繼承的類)的構(gòu)造函數(shù),否則可能無(wú)法正確地初始化對(duì)象。
請(qǐng)看下面的Bird類:
class Bird:
def __init__(self):
self.hungry = True
def eat(self):
if self.hungry:
print('Aaaah ...')
self.hungry = False
else:
print('No, thanks!')
這個(gè)類定義了所有鳥都具備的一種基本能力:進(jìn)食。下面的示例演示了如何使用這個(gè)類:
>>> b = Bird()
>>> b.eat()
Aaaah ...
>>> b.eat()
No, thanks!
從這個(gè)示例可知,鳥進(jìn)食后就不再饑餓。下面來(lái)看子類SongBird,它新增了鳴叫功能。
class SongBird(Bird):
def __init__(self):
self.sound = 'Squawk!'
def sing(self):
print(self.sound)
SongBird類使用起來(lái)與Bird類一樣容易:
>>> sb = SongBird()
>>> sb.sing()
Squawk!
9.1.2 調(diào)用未關(guān)聯(lián)的超類構(gòu)造函數(shù)
class SongBird(Bird):
def __init__(self):
Bird.__init__(self)
self.sound = 'Squawk!'
def sing(self):
print(self.sound)
在SongBird類中,只添加了一行,其中包含代碼Bird.init(self)。
>>> sb = SongBird()
>>> sb.sing()
Squawk!
>>> sb.eat()
Aaaah ...
>>> sb.eat()
No, thanks!
對(duì)實(shí)例調(diào)用方法時(shí),方法的參數(shù)self將自動(dòng)關(guān)聯(lián)到實(shí)例(稱為關(guān)聯(lián)的方法),這樣的示例你見(jiàn)過(guò)多個(gè)。然而,如果你通過(guò)類調(diào)用方法(如Bird.init),就沒(méi)有實(shí)例與其相關(guān)聯(lián)。在這種情況下,你可隨便設(shè)置參數(shù)self。這樣的方法稱為未關(guān)聯(lián)的。
通過(guò)將這個(gè)未關(guān)聯(lián)方法的self參數(shù)設(shè)置為當(dāng)前實(shí)例,將使用超類的構(gòu)造函數(shù)來(lái)初始化SongBird對(duì)象。這意味著將設(shè)置其屬性hungry。
9.1.3 使用函數(shù)super
調(diào)用這個(gè)函數(shù)時(shí),將當(dāng)前類和當(dāng)前實(shí)例作為參數(shù)。對(duì)其返回的對(duì)象調(diào)用方法時(shí),調(diào)用的將是超類(而不是當(dāng)前類)的方法。因此,在SongBird的構(gòu)造函數(shù)中,可不使用Bird,而是使用super(SongBird, self)。另外,可像通常那樣(也就是像調(diào)用關(guān)聯(lián)的方法那樣)調(diào)用方法init。
下面是前述示例的修訂版本:
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().__init__()
self.sound = 'Squawk!'
def sing(self):
print(self.sound)
這個(gè)新式版本與舊式版本等效:
>>> sb = SongBird()
>>> sb.sing()
Squawk!
>>> sb.eat()
Aaaah ...
>>> sb.eat()
No, thanks!
9.2 元素訪問(wèn)
雖然init無(wú)疑是你目前遇到的最重要的特殊方法,但還有不少其他的特殊方法,讓你能夠完成很多很酷的任務(wù)。
9.2.1 基本的序列和映射協(xié)議
序列和映射基本上是元素(item)的集合,要實(shí)現(xiàn)它們的基本行為(協(xié)議),不可變對(duì)象需要實(shí)現(xiàn)2個(gè)方法,而可變對(duì)象需要實(shí)現(xiàn)4個(gè)。
? len(self):這個(gè)方法應(yīng)返回集合包含的項(xiàng)數(shù),對(duì)序列來(lái)說(shuō)為元素個(gè)數(shù),對(duì)映射來(lái)說(shuō)為鍵?值對(duì)數(shù)。如果len返回零(且沒(méi)有實(shí)現(xiàn)覆蓋這種行為的nonzero),對(duì)象在布爾上下文中將被視為假(就像空的列表、元組、字符串和字典一樣)。
? getitem(self, key):這個(gè)方法應(yīng)返回與指定鍵相關(guān)聯(lián)的值。對(duì)序列來(lái)說(shuō),鍵應(yīng)該是0~n -1的整數(shù)(也可以是負(fù)數(shù),這將在后面說(shuō)明),其中n為序列的長(zhǎng)度。對(duì)映射來(lái)說(shuō),鍵可以是任何類型。
? setitem(self, key, value):這個(gè)方法應(yīng)以與鍵相關(guān)聯(lián)的方式存儲(chǔ)值,以便以后能夠使用getitem來(lái)獲取。當(dāng)然,僅當(dāng)對(duì)象可變時(shí)才需要實(shí)現(xiàn)這個(gè)方法。
?delitem(self, key):這個(gè)方法在對(duì)對(duì)象的組成部分使用del語(yǔ)句時(shí)被調(diào)用,應(yīng)刪除與key相關(guān)聯(lián)的值。同樣,僅當(dāng)對(duì)象可變(且允許其項(xiàng)被刪除)時(shí),才需要實(shí)現(xiàn)這個(gè)方法。
對(duì)于這些方法,還有一些額外的要求。
? 對(duì)于序列,如果鍵為負(fù)整數(shù),應(yīng)從末尾往前數(shù)。換而言之,x[-n]應(yīng)與x[len(x)-n]等效。
? 如果鍵的類型不合適(如對(duì)序列使用字符串鍵),可能引發(fā)TypeError異常。
? 對(duì)于序列,如果索引的類型是正確的,但不在允許的范圍內(nèi),應(yīng)引發(fā)IndexError異常。
要了解更復(fù)雜的接口和使用的抽象基類(Sequence),請(qǐng)參閱有關(guān)模塊collections的文檔。
下面來(lái)試一試,看看能否創(chuàng)建一個(gè)無(wú)窮序列。
def check_index(key):
"""
指定的鍵是否是可接受的索引?
鍵必須是非負(fù)整數(shù),才是可接受的。如果不是整數(shù),
將引發(fā)TypeError異常;如果是負(fù)數(shù),將引發(fā)Index
Error異常(因?yàn)檫@個(gè)序列的長(zhǎng)度是無(wú)窮的)
"""
if not isinstance(key, int): raise TypeError #判斷一個(gè)對(duì)象是否是一個(gè)已知的類型
if key < 0: raise IndexError
class ArithmeticSequence:
def __init__(self, start=0, step=1):
"""
初始化這個(gè)算術(shù)序列
start -序列中的第一個(gè)值
step -兩個(gè)相鄰值的差
changed -一個(gè)字典,包含用戶修改后的值
"""
self.start = start # 存儲(chǔ)起始值
self.step = step # 存儲(chǔ)步長(zhǎng)值
self.changed = {} # 沒(méi)有任何元素被修改
def __getitem__(self, key):
"""
從算術(shù)序列中獲取一個(gè)元素
"""
check_index(key)
try: return self.changed[key] # 修改過(guò)?
except KeyError: # 如果沒(méi)有修改過(guò),
return self.start + key * self.step # 就計(jì)算元素的值
def __setitem__(self, key, value):
"""
修改算術(shù)序列中的元素
"""
check_index(key)
self.changed[key] = value # 存儲(chǔ)修改后的值
這些代碼實(shí)現(xiàn)的是一個(gè)算術(shù)序列,其中任何兩個(gè)相鄰數(shù)字的差都相同。第一個(gè)值是由構(gòu)造函數(shù)的參數(shù)start(默認(rèn)為0)指定的,而相鄰值之間的差是由參數(shù)step(默認(rèn)為1)指定的。你允許用戶修改某些元素,這是通過(guò)將不符合規(guī)則的值保存在字典changed中實(shí)現(xiàn)的。如果元素未被修改,就使用公式self.start + key * self.step來(lái)計(jì)算它的值。
下面的示例演示了如何使用這個(gè)類:
>>> s = ArithmeticSequence(1, 2)
>>> s[4]
9
>>> s[4] = 2
>>> s[4]
2
>>> s[5]
11
9.2.2 從 list、dict 和 str 派生
來(lái)看一個(gè)簡(jiǎn)單的示例——一個(gè)帶訪問(wèn)計(jì)數(shù)器的列表。
class CounterList(list):
def __init__(self, *args):
super().__init__(*args)
self.counter = 0
def __getitem__(self, index):
self.counter += 1
return super(CounterList, self).__getitem__(index)
CounterList類深深地依賴于其超類(list)的行為。CounterList沒(méi)有重寫的方法(如append、extend、index等)都可直接使用。在兩個(gè)被重寫的方法中,使用super來(lái)調(diào)用超類的相應(yīng)方法,并添加了必要的行為:初始化屬性counter(在init中)和更新屬性counter(在getitem中)。
下面的示例演示了CounterList的可能用法:
>>> cl = CounterList(range(10))
>>> cl
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> cl.reverse()
>>> cl
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
>>> del cl[3:6]
>>> cl
[9, 8, 7, 3, 2, 1, 0]
>>> cl.counter
0
>>> cl[4] + cl[2]
9
>>> cl.counter
2
如你所見(jiàn),CounterList的行為在大多數(shù)方面都類似于列表,但它有一個(gè)counter屬性(其初始值為0)。每當(dāng)你訪問(wèn)列表元素時(shí),這個(gè)屬性的值都加1。執(zhí)行加法運(yùn)算cl[4] + cl[2]后,counter的值遞增兩次,變成了2。
9.3 特性
如果訪問(wèn)給定屬性時(shí)必須采取特定的措施,那么像這樣封裝狀態(tài)變量(屬性)很重要。例如,請(qǐng)看下面的Rectangle類:
class Rectangle:
def __init__(self):
self.width = 0
self.height = 0
def set_size(self, size):
self.width, self.height = size
def get_size(self):
return self.width, self.height
下面的示例演示了如何使用這個(gè)類:
>>> r = 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的存取方法,這個(gè)屬性是一個(gè)由width和height組成的元組。(可隨便將這個(gè)屬性替換為更有趣的屬性,如矩形的面積或其對(duì)角線長(zhǎng)度。)這些代碼并非完全錯(cuò)誤,但存在缺陷。使用這個(gè)類時(shí),程序員應(yīng)無(wú)需關(guān)心它是如何實(shí)現(xiàn)的(封裝)。如果有一天你想修改實(shí)現(xiàn),讓size成為真正的屬性,而width和height是動(dòng)態(tài)計(jì)算出來(lái)的,就需要提供用于訪問(wèn)width和height的存取方法,使用這個(gè)類的程序也必須重寫。應(yīng)讓客戶端代碼(使用你所編寫代碼的代碼)能夠以同樣的方式對(duì)待所有的屬性。
9.3.1 函數(shù) property
函數(shù)property使用起來(lái)很簡(jiǎn)單。如果你編寫了一個(gè)類,如前一節(jié)的Rectangle類,只需再添加一行代碼。
class Rectangle:
def __init__(self):
self.width = 0
self.height = 0
def set_size(self, size):
self.width, self.height = size
def get_size(self):
return self.width, self.height
size = property(get_size, set_size)
在這個(gè)新版的Rectangle中,通過(guò)調(diào)用函數(shù)property并將存取方法作為參數(shù)(獲取方法在前,設(shè)置方法在后)創(chuàng)建了一個(gè)特性,然后將名稱size關(guān)聯(lián)到這個(gè)特性。這樣,你就能以同樣的方式對(duì)待width、height和size,而無(wú)需關(guān)心它們是如何實(shí)現(xiàn)的。
>>> r = Rectangle()
>>> r.width = 10
>>> r.height = 5
>>> r.size
(10, 5)
>>> r.size = 150, 100
>>> r.width
150
如你所見(jiàn),屬性size依然受制于get_size和set_size執(zhí)行的計(jì)算,但看起來(lái)就像普通屬性一樣。
實(shí)際上,調(diào)用函數(shù)property時(shí),還可不指定參數(shù)、指定一個(gè)參數(shù)、指定三個(gè)參數(shù)或指定四個(gè)參數(shù)。如果沒(méi)有指定任何參數(shù),創(chuàng)建的特性將既不可讀也不可寫。如果只指定一個(gè)參數(shù)(獲取方法),創(chuàng)建的特性將是只讀的。第三個(gè)參數(shù)是可選的,指定用于刪除屬性的方法(這個(gè)方法不接受任何參數(shù))。第四個(gè)參數(shù)也是可選的,指定一個(gè)文檔字符串。這些參數(shù)分別名為fget、fset、fdel和doc。如果你要?jiǎng)?chuàng)建一個(gè)只可寫且?guī)臋n字符串的特性,可使用它們作為關(guān)鍵字參數(shù)來(lái)實(shí)現(xiàn)。
9.3.2 靜態(tài)方法和類方法
靜態(tài)方法和類方法是這樣創(chuàng)建的:將它們分別包裝在staticmethod和classmethod類的對(duì)象中。靜態(tài)方法的定義中沒(méi)有參數(shù)self,可直接通過(guò)類來(lái)調(diào)用。類方法的定義中包含類似于self的參數(shù),通常被命名為cls。對(duì)于類方法,也可通過(guò)對(duì)象直接調(diào)用,但參數(shù)cls將自動(dòng)關(guān)聯(lián)到類。下面是一個(gè)簡(jiǎn)單的示例:
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)
像這樣手工包裝和替換方法有點(diǎn)繁瑣。在Python 2.4中,引入了一種名為裝飾器的新語(yǔ)法,可用于像這樣包裝方法。(實(shí)際上,裝飾器可用于包裝任何可調(diào)用的對(duì)象,并且可用于方法和函數(shù)。)可指定一個(gè)或多個(gè)裝飾器,為此可在方法(或函數(shù))前面使用運(yùn)算符@列出這些裝飾(指定了多個(gè)裝飾器時(shí),應(yīng)用的順序與列出的順序相反)。
class MyClass:
@staticmethod
def smeth():
print('This is a static method')
@classmethod
def cmeth(cls):
print('This is a class method of', cls)
定義這些方法后,就可像下面這樣使用它們(無(wú)需實(shí)例化類):
>>> MyClass.smeth()
This is a static method
>>> MyClass.cmeth()
This is a class method of <class '__main__.MyClass'>
9.3.3 getattr、setattr等方法
可以攔截對(duì)對(duì)象屬性的所有訪問(wèn)企圖,其用途之一是在舊式類中實(shí)現(xiàn)特性(在舊式類中,函數(shù)property的行為可能不符合預(yù)期)。要在屬性被訪問(wèn)時(shí)執(zhí)行一段代碼,必須使用一些魔法法。下面的四個(gè)魔法方法提供了你需要的所有功能(在舊式類中,只需使用后面三個(gè))。
? getattribute(self, name):在屬性被訪問(wèn)時(shí)自動(dòng)調(diào)用(只適用于新式類)。
? getattr(self, name):在屬性被訪問(wèn)而對(duì)象沒(méi)有這樣的屬性時(shí)自動(dòng)調(diào)用。
? setattr(self, name, value):試圖給屬性賦值時(shí)自動(dòng)調(diào)用。
? delattr(self, name):試圖刪除屬性時(shí)自動(dòng)調(diào)用。
相比函數(shù)property,這些魔法方法使用起來(lái)要棘手些(從某種程度上說(shuō),效率也更低),但它們很有用,因?yàn)槟憧稍谶@些方法中編寫處理多個(gè)特性的代碼。然而,在可能的情況下,還是使用函數(shù)property吧。
再來(lái)看前面的Rectangle示例,但這里使用的是魔法方法:
class Rectangle:
def __init__ (self):
self.width = 0
self.height = 0
def __setattr__(self, name, value):
if name == 'size':
self.width, self.height = value
else:
self. __dict__[name] = value
def __getattr__(self, name):
if name == 'size':
return self.width, self.height
else:
raise AttributeError()
如你所見(jiàn),這個(gè)版本需要處理額外的管理細(xì)節(jié)。對(duì)于這個(gè)代碼示例,需要注意如下兩點(diǎn)。

9.4 迭代器
9.4.1 迭代器協(xié)議
迭代(iterate)意味著重復(fù)多次,就像循環(huán)那樣。本書前面只使用for循環(huán)迭代過(guò)序列和字典,但實(shí)際上也可迭代其他對(duì)象:實(shí)現(xiàn)了方法iter的對(duì)象。
方法iter返回一個(gè)迭代器,它是包含方法next的對(duì)象,而調(diào)用這個(gè)方法時(shí)可不提供任何參數(shù)。當(dāng)你調(diào)用方法next時(shí),迭代器應(yīng)返回其下一個(gè)值。如果迭代器沒(méi)有可供返回的值,應(yīng)引發(fā)StopIteration異常。你還可使用內(nèi)置的便利函數(shù)next,在這種情況下,next(it)與it.next()等效。
這有什么意義呢?為何不使用列表呢?因?yàn)樵诤芏嗲闆r下,使用列表都有點(diǎn)像用大炮打蚊子。例如,如果你有一個(gè)可逐個(gè)計(jì)算值的函數(shù),你可能只想逐個(gè)地獲取值,而不是使用列表一次性獲取。這是因?yàn)槿绻泻芏嘀?,列表可能占用太多的?nèi)存。但還有其他原因:使用迭代器更通用、更簡(jiǎn)單、更優(yōu)雅。下面來(lái)看一個(gè)不能使用列表的示例,因?yàn)槿绻褂?,這個(gè)列表的長(zhǎng)度必須是無(wú)窮大的!
這個(gè)“列表”為斐波那契數(shù)列,表示該數(shù)列的迭代器如下:
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
注意到這個(gè)迭代器實(shí)現(xiàn)了方法iter,而這個(gè)方法返回迭代器本身。在很多情況下,都在另一個(gè)對(duì)象中實(shí)現(xiàn)返回迭代器的方法iter,并在for循環(huán)中使用這個(gè)對(duì)象。但推薦在迭代器中也實(shí)現(xiàn)方法iter(并像剛才那樣讓它返回self),這樣迭代器就可直接用于for循環(huán)中。
首先,創(chuàng)建一個(gè)Fibs對(duì)象。
>>> fibs = Fibs()
>>> for f in fibs:
... if f > 1000:
... print(f)
... break
...
1597
這個(gè)循環(huán)之所以會(huì)停止,是因?yàn)槠渲邪琤reak語(yǔ)句;否則,這個(gè)for循環(huán)將沒(méi)完沒(méi)了地執(zhí)行。
9.4.2 從迭代器創(chuàng)建序列
除了對(duì)迭代器和可迭代對(duì)象進(jìn)行迭代(通常這樣做)之外,還可將它們轉(zhuǎn)換為序列。在可以使用序列的情況下,大多也可使用迭代器或可迭代對(duì)象(諸如索引和切片等操作除外)。一個(gè)這樣的例子是使用構(gòu)造函數(shù)list顯式地將迭代器轉(zhuǎn)換為列表。
>>> 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.5 生成器
生成器是一種使用普通函數(shù)語(yǔ)法定義的迭代器。生成器的工作原理到底是什么呢?通過(guò)示例來(lái)說(shuō)明最合適。下面先來(lái)看看如何創(chuàng)建和使用生成器,然后再看看幕后的情況。
9.5.1 創(chuàng)建生成器
生成器創(chuàng)建起來(lái)與函數(shù)一樣簡(jiǎn)單。你現(xiàn)在肯定厭煩了老套的斐波那契數(shù)列,所以下面換換口味,創(chuàng)建一個(gè)將嵌套列表展開的函數(shù)。這個(gè)函數(shù)將一個(gè)類似于下面的列表作為參數(shù):
nested = [[1, 2], [3, 4], [5]]
換而言之,這是一個(gè)列表的列表。函數(shù)應(yīng)按順序提供這些數(shù)字,下面是一種解決方案:
def flatten(nested):
for sublist in nested:
for element in sublist:
yield element
這個(gè)函數(shù)的大部分代碼都很簡(jiǎn)單。它首先迭代所提供嵌套列表中的所有子列表,然后按順序迭代每個(gè)子列表的元素。
在這里,你沒(méi)有見(jiàn)過(guò)的是yield語(yǔ)句。包含yield語(yǔ)句的函數(shù)都被稱為生成器。這可不僅僅是名稱上的差別,生成器的行為與普通函數(shù)截然不同。差別在于,生成器不是使用return返回一值,而是可以生成多個(gè)值,每次一個(gè)。每次使用yield生成一個(gè)值后,函數(shù)都將凍結(jié),即在此停止執(zhí)行,等待被重新喚醒。被重新喚醒后,函數(shù)將從停止的地方開始繼續(xù)執(zhí)行。
為使用所有的值,可對(duì)生成器進(jìn)行迭代。
>>> nested = [[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]
9.5.2 遞歸式生成器
前一節(jié)設(shè)計(jì)的生成器只能處理兩層的嵌套列表,這是使用兩個(gè)for循環(huán)來(lái)實(shí)現(xiàn)的。如果要處理任意層嵌套的列表,該如何辦呢?例如,你可能使用這樣的列表來(lái)表示樹結(jié)構(gòu)(也可以使用特定的樹類,但策略是相同的)。對(duì)于每層嵌套,都需要一個(gè)for循環(huán),但由于不知道有多少層嵌套,你必須修改解決方案,使其更靈活。該求助于遞歸了。
def flatten(nested):
try:
for sublist in nested:
for element in flatten(sublist):
yield element
except TypeError:
yield nested
調(diào)用flatten時(shí),有兩種可能性(處理遞歸時(shí)都如此):基線條件和遞歸條件。在基線條件下,要求這個(gè)函數(shù)展開單個(gè)元素(如一個(gè)數(shù))。在這種情況下,for循環(huán)將引發(fā)TypeError異常(因?yàn)槟阍噲D迭代一個(gè)數(shù)),而這個(gè)生成器只生成一個(gè)元素。
9.5.3
如果你按前面的例子做了,就差不多知道了如何使用生成器。你知道,生成器是包含關(guān)鍵字yield的函數(shù),但被調(diào)用時(shí)不會(huì)執(zhí)行函數(shù)體內(nèi)的代碼,而是返回一個(gè)迭代器。每次請(qǐng)求值時(shí),都將執(zhí)行生成器的代碼,直到遇到y(tǒng)ield或return。yield意味著應(yīng)生成一個(gè)值,而return意味著生成器應(yīng)停止執(zhí)行(即不再生成值;僅當(dāng)在生成器調(diào)用return時(shí),才能不提供任何參數(shù))。
換而言之,生成器由兩個(gè)單獨(dú)的部分組成:生成器的函數(shù)和生成器的迭代器。生成器的函數(shù)是由def語(yǔ)句定義的,其中包含yield。生成器的迭代器是這個(gè)函數(shù)返回的結(jié)果。用不太準(zhǔn)確的話說(shuō),這兩個(gè)實(shí)體通常被視為一個(gè),通稱為生成器。
>>> def simple_generator():
yield 1
...
>>> simple_generator
<function simple_generator at 153b44>
>>> simple_generator()
<generator object at 1510b0>
對(duì)于生成器的函數(shù)返回的迭代器,可以像使用其他迭代器一樣使用它。