《python基礎(chǔ)教程(第三版)》第九章 魔法方法、特性和迭代器

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ù)返回的迭代器,可以像使用其他迭代器一樣使用它。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容