當前位置: 首頁 ? 深入 Python 3 ?

難度級別: ?????

閉合 生成器

? My spelling is Wobbly. It’s good spelling but it Wobbles, and the letters get in the wrong places. ?
— Winnie-the-Pooh

 

深入

出于傳遞所有理解的原因,我一直對語言非常著迷。我指的不是編程語言。好吧,是編程語言,但同時也是自然語言。使用英語。英語是一種七拼八湊的語言,它從德語、法語、西班牙語和拉丁語(等等)語言中借用了大量詞匯。事實上,“借用”是不恰當的詞匯,“掠奪”更加符合。或者也許叫“同化“——就像博格人(譯注:根據維基百科資料,Borg 是《星際旅行》虛構宇宙中的一個種族,該譯法未經原作者映證)。是的,我喜歡這樣。

我們就是博格人。你們的語言和詞源特性將會被添加到我們自己的當中。抵抗是徒勞的。

在本章中,將開始學習復數名詞。以及返回其它函數的函數、高級正則表達式和生成器。但首先,讓我們聊聊如何生成復數名詞。(如果還沒有閱讀《正則表達式》一章,現在也許是個好時機讀一讀。本章將假定您理解了正則表達式的基礎,并迅速進入更高級的用法。)

如果在講英語的國家長大,或在正規的學校學習過英語,您可能對下面的基本規則很熟悉 :

(我知道,還有許多例外情況。Man 變成 menwoman 變成 women,但是 human 變成 humansMouse 變成 micelouse 變成 lice,但 house 變成 housesKnife 變成 kniveswife 變成 wives,但是 lowlife 變成 lowlifes。而且甚至我還沒有開始提到那些原型和復數形式相同的單詞,就像 sheepdeerhaiku。)

其它語言,當然是完全不同的。

讓我們設計一個 Python 類庫用來自動進行英語名詞的復數形式轉換。我們將以這四條規則為起點,但要記住的不可避免地還要增加更多規則。

?

我知道,讓我們用正則表達式!

因此,您正在看著單詞,至少是英語單詞,也就是說您正在看著字符的字符串。規則說你必須找到不同的字符組合,然后進行不同的處理。這聽起來是正則表達式的工作!

[下載 plural1.py]

import re

def plural(noun):          
    if re.search('[sxz]$', noun):             
        return re.sub('$', 'es', noun)        
    elif re.search('[^aeioudgkprt]h$', noun):
        return re.sub('$', 'es', noun)       
    elif re.search('[^aeiou]y$', noun):      
        return re.sub('y$', 'ies', noun)     
    else:
        return noun + 's'
  1. 這是一條正則表達式,但它使用了在 《正則表達式》 一章中沒有講過的語法。中括號表示“匹配這些字符的其中之一”。因此 [sxz] 的意思是: “sxz”,但只匹配其中之一。對 $ 應該很熟悉了,它匹配字符串的結尾。經過組合,該正則表達式將測試 noun 是否以 sxz 結尾。
  2. re.sub() 函數執行基于正則表達式的字符串替換。

讓我們看看正則表達式替換的細節。

>>> import re
>>> re.search('[abc]', 'Mark')    
<_sre.SRE_Match object at 0x001C1FA8>
>>> re.sub('[abc]', 'o', 'Mark')  
'Mork'
>>> re.sub('[abc]', 'o', 'rock')  
'rook'
>>> re.sub('[abc]', 'o', 'caps')  
'oops'
  1. 字符串 Mark 包含 abc 嗎?是的,它包含 a
  2. 好了,現在查找 abc,并將其替換為 oMark 變成了 Mork
  3. 同一函數將 rock 轉換為 rook
  4. 您可能會認為該函數會將 caps 轉換為 oaps,但實際上并是這樣。re.sub 替換 所有的 匹配項,而不僅僅是第一個匹配項。因此該正則表達式將 caps 轉換為 oops,因為無論是 c 還是 a 均被轉換為 o

接下來,回到 plural() 函數……

def plural(noun):          
    if re.search('[sxz]$', noun):            
        return re.sub('$', 'es', noun)         
    elif re.search('[^aeioudgkprt]h$', noun):  
        return re.sub('$', 'es', noun)
    elif re.search('[^aeiou]y$', noun):        
        return re.sub('y$', 'ies', noun)     
    else:
        return noun + 's'
  1. 此處將字符串的結尾(通過 $ 匹配)替換為字符串 es 。換句話來說,向字符串尾部添加一個 es 。可以通過字符串鏈接來完成同樣的變化,例如 noun + 'es',但我對每條規則都選用正則表達式,其原因將在本章稍后更加清晰。
  2. 仔細看看,這里出現了新的變化。作為方括號中的第一個字符, ^ 有特別的含義:非。[^abc] 的意思是:“ 除了 abc 之外的任何字符”。因此 [^aeioudgkprt] 的意思是除了 aeioudgkprt 之外的任何字符。然后該字符必須緊隨一個 h,其后是字符串的結尾。所匹配的是以 H 結尾且 H 發音的單詞。
  3. 此處有同樣的模式:匹配以 Y 結尾的單詞,而 Y 之前的字符 不是 aeiou。所匹配的是以 Y 結尾,且 Y 發音聽起來像 I 的單詞。

讓我們看看“否定”正則表達式的更多細節。

>>> import re
>>> re.search('[^aeiou]y$', 'vacancy')  
<_sre.SRE_Match object at 0x001C1FA8>
>>> re.search('[^aeiou]y$', 'boy')      
>>> 
>>> re.search('[^aeiou]y$', 'day')
>>> 
>>> re.search('[^aeiou]y$', 'pita')     
>>> 
  1. vacancy 匹配該正則表達式,因為它以 cy 結尾,且 c 并非 aeiou
  2. boy 不匹配,因為它以 oy 結尾,可以明確地說 y 之前的字符不能是 oday 不匹配,因為它以 ay 結尾。
  3. pita 不匹配,因為它不以 y 結尾。
>>> re.sub('y$', 'ies', 'vacancy')               
'vacancies'
>>> re.sub('y$', 'ies', 'agency')
'agencies'
>>> re.sub('([^aeiou])y$', r'\1ies', 'vacancy')  
'vacancies'
  1. 該正則表達式將 vacancy 轉換為 vacancies ,將 agency 轉換為 agencies,這正是想要的結果。注意,它也會將 boy 轉換為 boies,但這永遠也不會在函數中發生,因為我們首先進行了 re.search 以找出永遠不應進行該 re.sub 操作的單詞。
  2. 順便,我還想指出可以將該兩條正則表達式合并起來(一條查找是否應用該規則,另一條實際應用規則),使其成為一條正則表達式。它看起來是下面這個樣子:其中多數內容看起來應該很熟悉:使用了在 案例研究:分析電話號碼 中用到的記憶分組。該分組用于保存字母 y 之前的字符。然后在替換字符串中,用到了新的語法: \1,它表示“嘿,記住的第一個分組呢?把它放到這里。”在此例中, 記住了 y 之前的 c ,在進行替換時,將用 c 替代 c,用 ies 替代 y 。(如果有超過一個的記憶分組,可以使用 \2\3 等等。)

正則表達式替換功能非常強大,而 \1 語法則使之愈加強大。但是,將整個操作組合成一條正則表達式也更難閱讀,而且也沒有直接映射到剛才所描述的復數規則。剛才所闡述的規則,像 “如果單詞以 S 、X 或 Z 結尾,則添加 ES 。”如果查看該函數,有兩行代碼都在表述“如果以 S 、X 或 Z 結尾,那么添加 ES 。”它沒有之前那種模式更直接。

?

函數列表

現在要增加一些抽象層次的內容。我們開始時定義了一系列規則:如果這樣,那樣做;否則前往下一條規則。現在讓我們對部分程序進行臨時的復雜化,以簡化另一部分。

[download plural2.py]

import re

def match_sxz(noun):
    return re.search('[sxz]$', noun)

def apply_sxz(noun):
    return re.sub('$', 'es', noun)

def match_h(noun):
    return re.search('[^aeioudgkprt]h$', noun)

def apply_h(noun):
    return re.sub('$', 'es', noun)

def match_y(noun):                             
    return re.search('[^aeiou]y$', noun)
        
def apply_y(noun):                             
    return re.sub('y$', 'ies', noun)

def match_default(noun):
    return True

def apply_default(noun):
    return noun + 's'

rules = ((match_sxz, apply_sxz),               
         (match_h, apply_h),
         (match_y, apply_y),
         (match_default, apply_default)
         )

def plural(noun):           
    for matches_rule, apply_rule in rules:       
        if matches_rule(noun):
            return apply_rule(noun)
  1. 現在,每條匹配規則都有自己的函數,它們返回對 re.search() 函數調用結果。
  2. 每條應用規則也都有自己的函數,它們調用 re.sub() 函數以應用恰當的復數變化規則。
  3. 現在有了一個 rules 數據結構——一個函數對的序列,而不是一個函數(plural())實現多個條規則。
  4. 由于所有的規則被分割成單獨的數據結構,新的 plural() 函數可以減少到幾行代碼。使用 for 循環,可以一次性從 rules 這個數據結構中取出匹配規則和應用規則這兩樣東西(一條匹配對應一條應用)。在 for 循環的第一次迭代過程中, matches_rule 將獲取 match_sxz,而 apply_rule 將獲取 apply_sxz。在第二次迭代中(假定可以進行到這一步), matches_rule 將會賦值為 match_h,而 apply_rule 將會賦值為 apply_h 。該函數確保最終能夠返回某個值,因為終極匹配規則 (match_default) 只返回 True,意思是對應的應用規則 (apply_default) 將總是被應用。

該技術能夠成功運作的原因是 Python 中一切都是對象,包括了函數。數據結構 rules 包含了函數——不是函數的名稱,而是實際的函數對象。在 for 循環中被賦值后,matches_ruleapply_rule 是可實際調用的函數。在第一次 for 循環的迭代過程中,這相當于調用 matches_sxz(noun),如果返回一個匹配值,將調用 apply_sxz(noun)

如果這種附加抽象層令你迷惑,可以試著展開函數以了解其等價形式。整個 for 循環等價于下列代碼:


def plural(noun):
    if match_sxz(noun):
        return apply_sxz(noun)
    if match_h(noun):
        return apply_h(noun)
    if match_y(noun):
        return apply_y(noun)
    if match_default(noun):
        return apply_default(noun)

這段代碼的好處是 plural() 函數被簡化了。它處理一系列其它地方定義的規則,并以通用的方式對它們進行迭代。

  1. 獲取某匹配規則
  2. 是否匹配?然后調用應用規則,并返回結果。
  3. 不匹配?返回步驟 1 。

這些規則可在任何地方以任何方式定義。plural() 函數并不關心。

現在,新增的抽象層是否值得呢?嗯,還沒有。讓我們考慮下要向函數中新增一條規則時該如何操作。在第一例中,將需要新增一條 if 語句到 plural() 函數中。在第二例中,將需要新增兩個函數, match_foo()apply_foo(),然后更新 rules 序列以指定新的匹配和應用函數按照其它規則按順序調用。

但是對于下一節來說,這只是一個跳板而已。讓我們繼續……

?

匹配模式列表

其實并不是真的有必要為每個匹配和應用規則定義各自的命名函數。它們從未直接被調用,而只是被添加到 rules 序列并從該處被調用。此外,每個函數遵循兩種模式的其中之一。所有的匹配函數調用 re.search(),而所有的應用函數調用 re.sub()。讓我們將模式排除在考慮因素之外,使新規則定義更加簡單。

[download plural3.py]

import re

def build_match_and_apply_functions(pattern, search, replace):
    def matches_rule(word):                                     
        return re.search(pattern, word)
    def apply_rule(word):                                       
        return re.sub(search, replace, word)
    return (matches_rule, apply_rule)                           
  1. build_match_and_apply_functions() 函數用于動態創建其它函數。它接受 patternsearchreplace 三個參數,并定義了 matches_rule() 函數,該函數通過傳給 build_match_and_apply_functions() 函數的 pattern 及傳遞給所創建的 matchs_rules() 函數的 word 調用 re.search() 函數,哇。
  2. 應用函數的創建工作采用了同樣的方式。應用函數只接受一個參數,并使用傳遞給 build_match_and_apply_functions() 函數的 searchreplace 參數、以及傳遞給要創建 apply_rule() 函數的 word 調用 re.sub()。在動態函數中使用外部參數值的技術稱為 閉合【closures】。基本上,常量的創建工作都在創建應用函數過程中完成:它接受一個參數 (word),但實際操作還加上了另外兩個值(searchreplace),該兩個值都在定義應用函數時進行設置。
  3. 最后,build_match_and_apply_functions() 函數返回一個包含兩個值的元組:即剛才所創建的兩個函數。在這些函數中定義的常量( match_rule() 函數中的 pattern 函數,apply_rule() 函數中的 searchreplace )與這些函數呆在一起,即便是在從 build_match_and_apply_functions() 中返回后也一樣。這真是非常酷的一件事情。

但如果此方式導致了難以置信的混亂(應該是這樣,它確實有點奇怪),在看看如何使用之后可能會清晰一些。

patterns = \                                                        
  (
    ('[sxz]$',           '$',  'es'),
    ('[^aeioudgkprt]h$', '$',  'es'),
    ('(qu|[^aeiou])y$',  'y$', 'ies'),
    ('$',                '$',  's')                                 
  )
rules = [build_match_and_apply_functions(pattern, search, replace)  
         for (pattern, search, replace) in patterns]
  1. 我們的復數形式“規則”現在被定義為 字符串 的元組的元組(而不是函數)。每個組的第一個字符串是在 re.search() 中用于判斷該規則是否匹配的正則表達式。各組中的第二和第三個字符串是在 re.sub() 中將實際用于使用規則將名詞轉換為復數形式的搜索和替換表達式。
  2. 此處的后備規則略有變化。在前例中,match_default() 函數僅返回 True,意思是如果更多的指定規則無一匹配,代碼將簡單地向給定詞匯的尾部添加一個 s。本例則進行了一些功能等同的操作。最后的正則表達式詢問單詞是否有一個結尾($ 匹配字符串的結尾)。當然,每個字符串都有一個結尾,甚至是空字符串也有,因此該規則將始終被匹配。因此,它實現了 match_default() 函數同樣的目的,始終返回 True:它確保了如果沒有更多的指定規則用于匹配,代碼將向給定單詞的尾部增加一個 s
  3. 本行代碼非常神奇。它以 patterns 中的字符串序列為參數,并將其轉換為一個函數序列。怎么做到的?通過將字符串“映射”到 build_match_and_apply_functions() 函數。也就是說,它接受每組三重字符串為參數,并將該三個字符串作為實參調用 build_match_and_apply_functions() 函數。 build_match_and_apply_functions() 函數返回一個包含兩個函數的元組。也就是說該 規則 最后的結尾與前例在功能上是等價的:一個元組列表,每個元組都是一對函數。第一個函數是調用 re.search() 的匹配函數;而第二個函數調用 re.sub() 的應用函數。

此版本腳本的最前面是主入口點—— plural() 函數。

def plural(noun):
    for matches_rule, apply_rule in rules:  
        if matches_rule(noun):
            return apply_rule(noun)
  1. 由于 規則 列表與前例中的一樣(實際上確實相同),因此毫不奇怪 plural() 函數基本沒有發生變化。它是完全通用的,它以規則函數列表為參數,并按照順序調用它們。它并不關系規則是如何定義的。在前例中,它們被定義為各自命名的函數。現在它們通過將 build_match_and_apply_functions() 函數的輸出映射為源字符串的列表來動態創建。這沒有任何關系; plural() 函數將以同樣方式運作。

?

匹配模式文件

目前,已經排除了重復代碼,增加了足夠的抽象性,因此復數形式規則可以字符串列表的形式進行定義。下一個邏輯步驟是將這些字符串放入一個單獨的文件中,因此可獨立于使用它們的代碼來進行維護。

首先,讓我們創建一份包含所需規則的文本文件。沒有花哨的數據結構,只有空白符分隔的三列字符串。將其命名為 plural4-rules.txt.

[download plural4-rules.txt]

[sxz]$               $    es
[^aeioudgkprt]h$     $    es
[^aeiou]y$          y$    ies
$                    $    s

下面看看如何使用該規則文件。

[download plural4.py]

import re

def build_match_and_apply_functions(pattern, search, replace):  
    def matches_rule(word):
        return re.search(pattern, word)
    def apply_rule(word):
        return re.sub(search, replace, word)
    return (matches_rule, apply_rule)

rules = []
with open('plural4-rules.txt', encoding='utf-8') as pattern_file:  
    for line in pattern_file:                                      
        pattern, search, replace = line.split(None, 3)             
        rules.append(build_match_and_apply_functions(              
                pattern, search, replace))
  1. build_match_and_apply_functions() 函數沒有發生變化。仍然使用了閉合技術:通過外部函數中定義的變量來動態創建兩個函數。
  2. 全局的 open() 函數打開文件并返回一個文件對象。此例中,將要打開的文件包含了名詞復數形式的模式字符串。with 語句創建了叫做 context【上下文】的東西:當 with 塊結束時,Python 將自動關閉文件,即便是在 with 塊中引發了例外也會這樣。在 《文件》 一章中將學到關于 with 塊和文件對象的更多內容。
  3. for line in <fileobject> 代碼從打開的文件中讀取數據,并將文本賦值給 line 變量。在 《文件》 一章中將學到更多關于讀取文件的內容。
  4. 文件中每行都有三個值,單它們通過空白分隔(制表符或空白,沒有區別)。要將它們分開,可使用字符串方法 split()split() 方法的第一個參數是 None,表示“對任何空白字符進行分隔(制表符或空白,沒有區別)”。第二個參數是 3,意思是“針對空白分隔三次,丟棄該行剩下的部分。”像 [sxz]$ $ es 這樣的行將被分割為列表 ['[sxz]$', '$', 'es'],意思是 pattern 獲得值 '[sxz]$'search 獲得值 '$',而 replace 獲得值 'es'。對于短短的一行代碼來說確實威力夠大的。
  5. 最后,將 patternsearchreplace 傳入 build_match_and_apply_functions() 函數,它將返回一個函數的元組。將該元組添加到 rules 列表,最終 rules 將儲存 plural() 函數所預期的匹配和應用函數列表。

此處的改進是將復數形式規則獨立地放到了一份外部文件中,因此可獨立于使用它的代碼單獨對規則進行維護。代碼是代碼,數據是數據,生活更美好。

?

生成器

如果有個通用 plural() 函數解析規則文件不就更棒了嗎?獲取規則,檢查匹配,應用相應的轉換,進入下一條規則。這是 plural() 函數所必須完成的事,也是 plural() 函數必須做的事。

[download plural5.py]

def rules(rules_filename):
    with open(rules_filename, encoding='utf-8') as pattern_file:
        for line in pattern_file:
            pattern, search, replace = line.split(None, 3)
            yield build_match_and_apply_functions(pattern, search, replace)

def plural(noun, rules_filename='plural5-rules.txt'):
    for matches_rule, apply_rule in rules(rules_filename):
        if matches_rule(noun):
            return apply_rule(noun)
    raise ValueError('no matching rule for {0}'.format(noun))

這段代碼到底是如何運作的?讓我們先看一個交互式例子。

>>> def make_counter(x):
...     print('entering make_counter')
...     while True:
...         yield x                    
...         print('incrementing x')
...         x = x + 1
... 
>>> counter = make_counter(2)          
>>> counter                            
<generator object at 0x001C9C10>
>>> next(counter)                      
entering make_counter
2
>>> next(counter)                      
incrementing x
3
>>> next(counter)                      
incrementing x
4
  1. make_counter 中出現的 yield 命令的意思是這不是一個普通的函數。它是一次生成一個值的特殊類型函數。可以將其視為可恢復函數。調用該函數將返回一個可用于生成連續 x 值的 生成器【Generator】
  2. 為創建 make_counter 生成器的實例,僅需像調用其它函數那樣對它進行調用。注意該調用并不實際執行函數代碼。可以這么說,是因為 make_counter() 函數的第一行調用了 print(),但實際并未打印任何內容。
  3. make_counter() 函數返回了一個生成器對象。
  4. next() 函數以一個生成器對象為參數,并返回其下一個值。對 counter 生成器第一次調用 next() ,它針對第一條 yield 語句執行 make_counter() 中的代碼,然后返回所產生的值。在此情況下,該代碼輸出將為 2,因其僅通過調用 make_counter(2) 對生成器進行初始創建。
  5. 對同一生成器對象反復調用 next() 將確切地從上次調用的位置開始繼續,直到下一條 yield 語句。所有的變量、局部數據等內容在 yield 時被保存,在 next() 時被恢復。下一行代碼等待被執行以調用 print() 以打印出 incrementing x 。之后,執行語句 x = x + 1。然后它繼續通過 while 再次循環,而它再次遇上的第一條語句是 yield x,該語句將保存所有一切狀態,并返回當前 x 的值(當前為 3)。
  6. 第二次調用 next(counter) 時,又進行了同樣的工作,但這次 x4

由于 make_counter 設置了一個無限循環,理論上可以永遠執行該過程,它將不斷遞增 x 并輸出數值。還是讓我們看一個更加實用的生成器用法。

斐波那奇生成器

[download fibonacci.py]

def fib(max):
    a, b = 0, 1          
    while a < max:
        yield a          
        a, b = b, a + b  
  1. 斐波那契序列是一系列的數字,每個數字都是其前兩個數字之和。它從 0 和 1 開始,初始時上升緩慢,但越來越快。啟動該序列需要兩個變量:從 0 開始的 a,和從 1 開始的 b
  2. a 是當前序列中的數字,因此對它進行 yield 操作。
  3. b 是序列中下一個數字,因此將它賦值給 a,但同時計算下一個值 (a + b) 并將其賦值給 b 以供稍后使用。注意該步驟是并行發生的;如果 a3b5,那么 a, b = b, a + b 將會把 a 設置 5b 之前的值),將 b 設置為 8ab 之前值的和)。

因此,現在有了一個連續輸出斐波那契數值的函數。當然,還可以使用遞歸來完成該功能,但這個方式更易于閱讀。同樣,它也與 for 循環合作良好。

>>> from fibonacci import fib
>>> for n in fib(1000):      
...     print(n, end=' ')    
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
>>> list(fib(1000))          
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987]
  1. 可以在 for 循環中直接使用像 fib() 這樣的生成器。for 循環將會自動調用 next() 函數,從 fib() 生成器獲取數值并賦值給 for 循環索引變量。(n
  2. 每經過一次 for 循環, nfib()yield 語句獲取一個新值,所需做的僅僅是輸出它。一旦 fib() 的數字用盡(a 大于 max,即本例中的 1000), for 循環將會自動退出。
  3. 這是一個很有用的用法:將一個生成器傳遞給 list() 函數,它將遍歷整個生成器(就像前例中的 for 循環)并返回所有數值的列表。

復數規則生成器

讓我們回到 plural5.py 看看該版本的 plural() 函數是如何運作的。

def rules(rules_filename):
    with open(rules_filename, encoding='utf-8') as pattern_file:
        for line in pattern_file:
            pattern, search, replace = line.split(None, 3)                   
            yield build_match_and_apply_functions(pattern, search, replace)  

def plural(noun, rules_filename='plural5-rules.txt'):
    for matches_rule, apply_rule in rules(rules_filename):                   
        if matches_rule(noun):
            return apply_rule(noun)
    raise ValueError('no matching rule for {0}'.format(noun))
  1. 此處沒有太神奇的代碼。由于規則文件中每行都靠包括以空白相間的三個值,因此使用 line.split(None, 3) 獲取三個“列”的值并將它們賦值給三個局部變量。
  2. 然后使用了 yield。 但生產了什么呢?通過老朋友—— build_match_and_apply_functions() 動態創建的兩個函數,這與之前的例子是一樣的。換而言之, rules()按照需求連續生成匹配和應用函數的生成器。
  3. 由于 rules() 是生成器,可直接在 for 循環中使用它。對 for 循環的第一次遍歷,可以調用 rules() 函數打開模式文件,讀取第一行,從該行的模式動態創建一個匹配函數和應用函數,然后生成動態創建的函數。對 for 循環的第二次遍歷,將會精確地回到 rules() 中上次離開的位置(在 for line in pattern_file 循環的中間)。要進行的第一項工作是讀取文件(仍處于打開狀態)的下一行,基于該行的模式動態創建另一匹配和應用函數,然后生成兩個函數。

通過第四步獲得了什么呢?啟動時間。在第四步中引入 plural4 模塊時,它讀取了整個模式文件,并創建了一份所有可能規則的列表,甚至在考慮調用 plural() 函數之前。有了生成器,可以輕松地處理所有工作:可以讀取規則,創建函數并試用它們,如果該規則可用甚至可以不讀取文件剩下的部分或創建更多的函數。

失去了什么?性能!每次調用 plural() 函數,rules() 生成器將從頭開始——這意味著重新打開模式文件,并從頭開始讀取,每次一行。

要是能夠兩全其美多好啊:最低的啟動成本(無需對 import 執行任何代碼),同時 最佳的性能(無需一次次地創建同一函數)。哦,還需將規則保存在單獨的文件中(因為代碼和數據要涇渭分明),還有就是永遠不必兩次讀取同一行。

要實現該目標,必須建立自己的生成器。在進行此工作之前,必須對 Python 的類進行學習。

?

深入閱讀

? 2001–9 Mark Pilgrim

<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            亚洲欧美在线