[PY3]——函式——生成器(yield關鍵字)

Jelly_lyj發表於2017-03-18

函式—生成器篇

 

1. 認識和區分可迭代or生成器

1.1 可迭代物件

當你建立了一個列表,你可以逐項地讀取這個列表,這叫做一個可迭代物件

當你使用一個列表生成式來建立一個列表的時候,就建立了一個可迭代的物件

所有可以使用  for..in..語法的叫做一個迭代器:例如列表,字串,檔案……

經常使用它們是因為我們可以如願的讀取其中的元素,但是你把所有的值都儲存到了記憶體中,如果你有大量資料的話這個方式並不是你想要的

mylist=[ x*x for x in range(3) ]
for i in mylist:
    print(i)

for i in mylist:
    print(i)
0
1
4
0
1

4

1.2 生成器

生成器是可以迭代的,但是你只可以讀取它一次,因為它並不把所有的值放在記憶體中,它是實時地生成資料

mygenerator=( x*x for x in range(3) )
for i in mygenerator:
    print(i)

for i in mygenerator:
    print(i)

0
1
4

 

2. 理解生成器 & yield 

2.1 生成器是惰性求值的

惰性求值也叫延遲求值,顧名思義就是表示式不會在它被繫結到變數之後就立即求值,而是等用到時再求值。

延遲求值的一個好處是能夠建立可計算的無限列表而沒有妨礙計算的無限迴圈或大小問題。

考慮到yield的特性:可在減少記憶體佔用、避免使用遞迴等場景時選擇yield

2.2  生成器函式的執行流程

# yield關鍵字的作用是把一個函式變成一個generator,稱為生成器函式。
# 生成器函式的返回值是生成器
* 生成器函式執行的時候,不會執行函式體 * 當next生成器的時候, 當前程式碼執行到之後的第一個yield,會彈出值,並且暫停函式 * 當再次next生成器的時候,從上次暫停處開始往下執行 * 當沒有多餘的yield的時候,會丟擲StopIteration異常,異常的value是函式的返回值

通過以下例子來理解generator執行流程:

def gen(x):
    if x!=0:
        yield x

mygenerator=gen(3)

print(mygenerator)  #當我們呼叫這個函式時,我們發現函式內部的程式碼並不立馬執行,而只是返回了一個生成器物件
#<generator object gen at 0x7f63ee065888>

for i in mygenerator: #當你使用for進行迭代時,函式內的程式碼才執行
     print(i)
#3

print(next(mygenerator)) #或者使用next()
#3
def gen(max):         # def gen():
    a,b=1,1              # do something (a)
    while a<max:
        yield a          # yield a # (b)
        a,b=b,a+b        # do something (c)

for n in  gen(15):    # for n in gen():
    print(n)            # do something with n (d)

def dedupe(items): seen = set() for item in items: if item not in seen: yield item seen.add(item) a = [1, 5, 2, 1, 9, 1, 5, 10] for n in dedupe(a): print(n) 1 5 2 9 10

執行大致過程(個人理解,有誤請指正):
        seen=set( )    生成器環境初始化
            |
     dedupe內的for迴圈
            |
        呼叫yield  item=1
            |
         print 1
            |  
          add 1
            |
     dedupe內的for迴圈
            |
        呼叫yield  item=5
            |
         print 5
            |
          add 5
            |
     dedupe內的for迴圈   
            |
        呼叫yield  item=2 
            |
         print 2 
            |
          add 2
            |
     dedupe內的for迴圈('1'已經在items裡所以不執行迴圈)
            |
     dedupe內的for迴圈
            |
        呼叫yield  item=9
            |
          ......
def gen():
    print('a')
    yield 1
    print('b')
    yield 2
    return 3

g=gen()

print(g)
#<generator object gen at 0x7f144845a308>

print(next(g))
#a
#1

print(next(g))
#b
#2

print(next(g))
#StopIteration: 3

 

3. 生成器作用場景

3.1  計數器的例子

# version-1
def counter():
    x=0
    while True:
        x+=1
        yield x

def inc(x):
    return next(x)

# version-2
def counter():
    x=0
    while True:
        x+=1
        yield x

def inc():
    c=counter()
    return lambda :next(c)

# version-3
def make_inc():
    def counter():
        x=0
        while True:
            x+=1
            yield x
    c=counter()
    return lambda :next(c)  ## 為什麼這裡不直接 return next(c) ##
def make_inc():
    def counter():
        x=0
        while True:
            x+=1
            yield x
    c=counter()
    return lambda :next(c)
# return lambda:next(c)的執行結果
incr=make_inc()
print(id(incr()))  #取1
#8939648
print(id(incr()))  #取2
#8939680


def make_inc():
    def counter():
        x=0
        while True:
            x+=1
            yield x
    c=counter()
    return next(c)
# return next(c)的執行結果
print(id(make_inc()))  #取1
#8939648
print(id(make_inc()))  #取1
#8939648

3.2  解決遞迴問題 — 生成斐波那契數列的例子

def fib(max):
    n,a,b=0,0,1
    while n<max:
        yield b
        a,b=b,a+b
        n+=1

for n in fib(5):
    print(n)

1
1
2
3
5

3.3  協程 —— 生成器的高階用法

程式、執行緒 ——在核心態排程的
協程 ——在使用者態排程(即使用者自己寫排程器)
    ——執行在一個執行緒之內,所以也被叫做輕量執行緒
    ——非搶佔式排程
排程 ——簡單的說就是由排程器來決定哪段程式碼佔用cpu時間

協程在python3已經進入標準庫了 ——asyncio
python3.
5 中加入了 asyn、 await延伸支援

 

參考文章

《Python yield使用淺析-廖雪峰(含通過yield實現檔案讀取的方法)》

《[譯]python關鍵字yield的解釋(stackoverflow)》

《python中的惰性求值》

相關文章