<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Python生成器那些事兒

          共 5370字,需瀏覽 11分鐘

           ·

          2021-10-03 21:12


          導(dǎo)讀

          讀完本文你會有兩點收獲:

          1. 了解Python生成器的工作原理。
          2. 學(xué)會查詢PEP(Python Enhancement Proposals)。讓你知道python的一些設(shè)計理念。
          3. 最后會通過碼農(nóng)工作中一個實際的例子,來了解不同實現(xiàn)的好壞。

          什么是生成器?

          生成器(Generator)是一個非常強大的迭代器。其按照一定的算法生成一個序列。

          包含yield的函數(shù)會返回一個生成器。生成器函數(shù)和普通函數(shù)看上去很像,不同的是生成器的返回值是用yield實現(xiàn)的。

          也有一種寫法是(expression for i in s if condition),注意兩邊是括號而不是方括號。這種寫法等效于

          def?a_generator():
          ????for?i?in?s:
          ????????if?condition:
          ????????????yield?expression

          我們先實現(xiàn)一個簡單的生成器來演示一下:

          def?gen():
          ????yield?3
          ????yield?1
          ????yield?4

          a?=?gen()

          next(a)?#?output?3
          next(a)?#?output?1
          next(a)?#?output?4
          next(a)
          #?Traceback?(most?recent?call?last):
          #???File?"",?line?1,?in?
          #?StopIteration

          通過help(a),你會看到a是一個generator object,而且實現(xiàn)了__iter____next__,符合迭代器協(xié)議。說明函數(shù)gen返回了一個迭代器

          可以用next來進行迭代,迭代到最后會返回一個StopIteration的異常。

          與普通迭代器不同的是,生成器只能迭代一次。如果我們接著上面的代碼重新迭代,會發(fā)現(xiàn)沒有任何輸出。

          for?item?in?a:
          ????print(item,?end='?')

          # output:?沒有任何輸出。

          生成器與yield關(guān)鍵字息息相關(guān),我們先來了解一下yield。

          yield的發(fā)展史

          yield in python2.3

          yield在python2.3引入。最早的功能只有一個,就是將值返回給調(diào)用方,然后停止執(zhí)行函數(shù),保存現(xiàn)場并且再下次調(diào)用時回復(fù)。對比普通的函數(shù),函數(shù)在return后就退出了。中斷然后恢復(fù)是yield的特異功能。

          yield in python2.5

          到了python2.5,yield又有了表達式的功能(詳細見:https://www.python.org/dev/peps/pep-0342/)。也就是

          a?=?yield?b

          需要注意的是,這個表達式其實有兩部分,一部分是右側(cè)的yield b,一部分是賦值操作=。按照運算優(yōu)先級來說,要先執(zhí)行yield b。因為yield的特性,返回b之后函數(shù)暫停,所以下一次yield前會執(zhí)行賦值操作,那么a到底被賦予了一個什么樣的值呢?

          這就要解釋PEP-0342為迭代器引入的一個新函數(shù),send在yield之后,程序暫停,然后a就等待下一次send的輸入。注意,send(None)next是等價的。如下示例:

          c?=?None

          def?gen2():
          ????global?c
          ????b?=?0
          ????a?=?yield?b
          ????yield
          ????c?=?yield?a

          x?=?gen2()
          x.send(None)?# output 0, a等待接受send來的值。
          x.send(3)????#?output?None,?輸出前執(zhí)行a=3
          x.send(None)?# output 3, c等待接受send來的值。
          x.send(5)????#?output?StopIteration,?同時輸出前c=5
          print(c)?????#?output?5

          這段代碼可以用如下流程來解釋,右側(cè)帶顏色的程序塊之間會暫停,并等待新的send信號:

          yield in python3.3

          python3.3中,新加了yield from這個操作。yield from g 基本等價于 for v in g: yield v,但是內(nèi)部幫忙實現(xiàn)了很多邊界處理,比如異常等。

          明白了yield的基本操作,那么生成器有什么用呢?

          Python中生成器的作用

          這里告訴大家一個小技巧,當你不知道python某個功能有什么用的時候,可以查一下PEP(Python Enhancement Proposals),中文叫《Python增強提 案》。這里面除了重要的通知外,還有一些新功能的描述,以及為什么要設(shè)計這個功能。

          目錄為:https://www.python.org/dev/peps/ 可以按關(guān)鍵字檢索。

          拿生成器來說,首次出行在PEP255《Simple Generators》(https://www.python.org/dev/peps/pep-0255/),在Motivation一欄詳細描述了其設(shè)計的動機。可以看出,生成器的設(shè)計初衷是要優(yōu)化生產(chǎn)者函數(shù)迭代+回調(diào)函數(shù)的場景。某些處理可能會使用生成器之前生產(chǎn)的值,會讓調(diào)用者的設(shè)計變復(fù)雜。而yield的中斷恢復(fù)機制讓迭代和調(diào)用者都變得更加自然。基于這一功能,可以很方便的實現(xiàn) 協(xié)程操作。

          所以說使用生成器有如下的好處:

          1. 實現(xiàn)協(xié)程(Coroutine),通過yield的中斷恢復(fù)功能,可以實現(xiàn)一個線程實現(xiàn)多任務(wù)的調(diào)度。沒有了線程切換和鎖,沒有了用戶態(tài)和內(nèi)核態(tài)的切換 ,性能上會提升不少,尤其是IO場景較多的情況下。關(guān)于協(xié)程內(nèi)容較多,而且有更好的方法(async/await),有時間另開一篇講。
          2. 一般情況下生成器比迭代速度要快一些。
          3. 代碼可讀性更高。
          4. 由于本身也是個迭代器,所以也擁有迭代器的優(yōu)點:惰性計算。不需要把所有的數(shù)據(jù)加載到內(nèi)存,而是即取即用。

          不適合生成器的場景:

          1. 多次讀取。此時生成器無法滿足,可以用list(a_generator)來轉(zhuǎn)換成list
          2. 隨機讀取。生成器沒有類似x[i]這樣的下標操作。
          3. 拼接字符串。''.join(alist)''.join(a_generator)更快。

          生成器現(xiàn)實中的例子

          比如要讀取一些網(wǎng)絡(luò)日志,然后統(tǒng)計發(fā)送的字節(jié)數(shù)。格式如下:

          127.0.0.1?-?-?[24/Feb/2008:00:08:59?-0600]?"GET?/ply/ply.html?HTTP/1.1"?200?97238
          192.168.0.1?-?-?[24/Feb/2008:00:08:59?-0600]?"GET?/favicon.ico?HTTP/1.1"?404?133

          最后一列是發(fā)送字節(jié)數(shù)。我們要把文件中每一行最后一列的數(shù)字累計求和。

          第一種方法,硬編碼直接實現(xiàn)。


          def?read_file_count_inside(filename):
          ????total?=?0
          ????with?open(filename)?as?wwwlog:
          ????????for?line?in?wwwlog:
          ????????????bytes_sent?=?line.rsplit(None,?1)[1]
          ????????????if?bytes_sent?!=?'-':
          ????????????????total?+=?int(bytes_sent)
          ????return?total

          #?User?time?(seconds):?13.09
          #?System?time?(seconds):?0.35
          #?Elapsed?(wall?clock)?time?(h:mm:ss?or?m:ss):?0:13.45
          #?Maximum?resident?set?size?(kbytes):?6880

          直接實現(xiàn)很好,但是復(fù)用性較差。假如我換個任務(wù),統(tǒng)計ip的頻次。那就要修改現(xiàn)有的邏輯,或者重寫一個類似的函數(shù)。

          第二種實現(xiàn),讀取和處理分開。

          def?read_file(filename):
          ????with?open(filename)?as?wwwlog:
          ????????return?wwwlog.readlines()

          def?count_bytes(line_list):
          ????total?=?0
          ????for?line?in?line_list:
          ????????bytes_sent?=?line.rsplit(None,?1)[1]
          ????????if?bytes_sent?!=?'-':
          ????????????total?+=?int(bytes_sent)
          ????return?total

          #?User?time?(seconds):?13.90
          #?System?time?(seconds):?2.87
          #?Elapsed?(wall?clock)?time?(h:mm:ss?or?m:ss):?0:16.78
          #?Maximum?resident?set?size?(kbytes):?2315280

          第二種邏輯清晰,可擴展性也好。但是占用內(nèi)存太恐怖。

          第三種實現(xiàn):callback函數(shù)

          G_TOTAL?=?0

          def?count_callback(line):
          ????global?G_TOTAL
          ????bytes_sent?=?line.rsplit(None,?1)[1]
          ????if?bytes_sent?!=?'-':
          ????????G_TOTAL?+=?int(bytes_sent)

          def?read_file_with_callback(filename,?callback_fn):
          ????with?open(filename)?as?wwwlog:
          ????????for?line?in?wwwlog:
          ????????????callback_fn(line)
          ????return?G_TOTAL

          #?User?time?(seconds):?15.18
          #?System?time?(seconds):?0.38
          #?Elapsed?(wall?clock)?time?(h:mm:ss?or?m:ss):?0:15.57
          #?Maximum?resident?set?size?(kbytes):?6880

          callback代碼也算清晰,但是不免保存一些現(xiàn)場的變量。

          第四種實現(xiàn):生成器

          def?read_file_gen(filename):
          ????with?open(filename)?as?wwwlog:
          ????????for?line?in?wwwlog:
          ????????????yield?line

          def?count_gen(generator):
          ????bytecolumn?=?(line.rsplit(None,?1)[1]?for?line?in?generator)
          ????bytes_sent?=?(int(x)?for?x?in?bytecolumn?if?x?!=?'-')
          ????return?sum(bytes_sent)

          #?User?time?(seconds):?14.92
          #?System?time?(seconds):?0.34
          #?Elapsed?(wall?clock)?time?(h:mm:ss?or?m:ss):?0:15.26
          #?Maximum?resident?set?size?(kbytes):?6884

          生成器實現(xiàn)就很舒服,可以說是綜合了第二第三種的優(yōu)點。

          第五種:走火入魔生成器

          sum(int(x)?for?x?in?(line.rsplit(None,?1)[1]?for?line?in?open(filename))?if?x?!=?'-')

          #?User?time?(seconds):?14.03
          #?System?time?(seconds):?0.32
          #?Elapsed?(wall?clock)?time?(h:mm:ss?or?m:ss):?0:14.35
          #?Maximum?resident?set?size?(kbytes):?6916

          這種方法雖然免去了很多函數(shù)調(diào)用,但是可讀性并不太好,只適合簡單場景。不建議使用。

          第六種:實際工作中采用的方法

          awk?{?total?+=?$NF?}?END?{?print?total?}?big-access-log
          #?User?time?(seconds):?10.72
          #?System?time?(seconds):?0.32
          #?Elapsed?(wall?clock)?time?(h:mm:ss?or?m:ss):?0:11.04
          #?Maximum?resident?set?size?(kbytes):?1348

          程序員總是想用最少的代碼來實現(xiàn)一個功能。對于文本處理來說,awk和sed可以滿足絕大多數(shù)的需求,而且速度比python更快。但是python勝在跨平臺,畢竟windows可沒有awk。

          你喜歡哪一種呢?





          瀏覽 16
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  一级黄色电影网 | 日韩无码蜜桃 | 91九色观看 | 国产成人黄色在线观看 | 台湾无码字幕无码 |