Python生成器 百日築基之堵漏

id_iot發表於2019-01-24

生成器
函式體內有yield選項的就是生成器,生成器的本質是迭代器,由於函式結構和生成器結構類似,可以通過呼叫判斷是函式還是生成器.如下:

def fun():
    yield "我是生成器"
print(fun())

# 列印內容如下:
<generator object fun at 0x0000000002160ED0> 

生成器的優點就是節省記憶體.
Python獲取生成器的二種方式:

  • 通過函式獲取生成器
  • 通過生成器推導式建立生成器

通過函式獲取生成器

def fun(): 
    print("fun") 
    yield "生成器" 
g = fun() 
print(g)    # 列印函式名檢視是否是生成器 

# 列印內容如下:
<generator object fun at 0x0000000000510ED0> 

從列印內容可以看出是生成器.但是發現生成器裡面的內容沒有被列印,那如何列印生成器內容呢?我們可以把生成器理解成迭代器的變異版,所以要列印生成器的內容,與迭代器類似,建立生成器物件後.可以使用生成器.__next__()來列印生成器內容.或者next(),send()等來列印生成器,如下:
使用.__next__()來列印生成器中的內容

def fun(): 
    print("fun") 
    yield "生成器" 
    print("我在生成器的下面") 
g = fun()       # 建立生成器物件 
print(g)        # 列印生成器物件 
print(g.__next__())    # 列印生成器裡面的內容 

# 列印內容如下:
<generator object fun at 0x0000000002200ED0>
fun 
生成器 

可以發現yield下面的print語句沒有被列印.到yield停止了

def fun(): 
    print("fun") 
    yield "生成器1" 
    print("我在生成器1下面") 
    yield "生成器2" 
    print("我在生成器2的下面") 
g = fun()    # 建立生成器物件 
print(g.__next__()) 
print(g.__next__()) 

# 列印內容如下:
fun 
生成器1 
我在生成器1下面 
生成器2 

由上面兩個事例可以得出一個總結:就是每next一次就執行一次yield上面的程式碼一次,yield下面的程式碼不會被執行,這就是生成器的惰性機制
使用next()列印生成器內容

def fun(): 
    print("fun") 
    yield "生成器" 
    print("我在生成器下面") 
    yield "生成器2" 
    print("我在生成器2的下面") 
g = fun() 
print(next(g)) # next(g)列印生成器內容 
print(next(g)) # next(g)列印生成器內容 

# 列印內容如下:
fun 
生成器 
我在生成器下面 
生成器2 

與.__next__()功能類似
使用send(引數)列印生成器內容:
send方法可以給上一層的yield傳遞一個值,如果上一個yield沒有值的話send的引數將被忽略,如果有值yield的值將被改變成當前的引數,還有需要注意的地方就是如果send(引數)做為第一次迭代,由於上一層沒有yield,所以沒有辦法傳參,會導致出現錯誤,錯誤內容如下:
TypeError: can`t send non-None value to a just-started generator
我們將send(None)作為第一次呼叫即可.然後在第二次呼叫時可以傳適當的引數.
如下:

def fun(): 
    print("fun") 
    val = yield "生成器" 
    print("我在生成器下面") 
    print(val) 
    yield "生成器2" 
    print("我在生成器2的下面") 
    yield "生成器3" 
    print("我在生成器3的下面") 
g = fun() 
print(g.send(None)) 
print(g.send("send")) 
print(g.send("send2")) 

# 列印內容如下:
fun 
生成器 
我在生成器下面 
send 
生成器2 
我在生成器2的下面 
生成器3 

生成器的基礎用法:
使用for迴圈列印生成器物件

def fun(): 
    print("fun") 
    yield "生成器" 
    print("我在生成器下面") 
    yield "生成器2" 
    print("我在生成器2的下面") 
    yield "生成器3" 
    print("我在生成器3的下面") 
g = fun() # 建立生成器物件 
for g_buf in g: # 使用for迴圈列印生成器物件 
    print(g_buf) 

# 列印內容如下
fun
生成器
我在生成器下面
生成器2
我在生成器2的下面
生成器3
我在生成器3的下面

yield可以返回任何資料型別,這裡以列表為事例

def fun(): 
    list_1 = [1,2,3,4,5] 
    yield list_1 # 將整個列表作為返回值傳給生成器物件
g = fun() # 建立生成器物件 
print(g.__next__()) # 列印生成器物件 

# 列印內容如下:
[1, 2, 3, 4, 5] 

如果想要yield從列表中每次返回一個元素使用yield from 列表來實現

def fun(): 
    list_1 = [1,2,3,4,5] 
    yield from list_1 
g = fun() # 建立生成器物件 
print(g.__next__()) # 列印生成器物件內容 

# 列印內容如下:
1 

可以發現只列印了列表中的一個元素.可以使用for迴圈列印所有內容:

def fun(): 
    list_1 = [1,2,3,4,5] 
    yield from list_1 
g = fun() 
for g_buf in g: 
print(g_buf) 

# 列印內容如下:
1 
2 
3 
4 
5 

相當於事項了5次print(g.__next__()) # 列印生成器物件內容

推導式:
列表推導式:
如給list_1列表賦值1-20,常規做法如下:

list_1 = [] 
for num in range(20): 
    list_1.append(num) 
print(list_1) 

# 列印內容如下:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

列表list_1和list_2簡單的推導式如下:

list_1 = [num for num in range(20)] 
list_2 = ["Python: %s" % num for num in range(5)]
print(list_1) 
print(list_2) 

# 列印內容如下:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
[`Python: 0`, `Python: 1`, `Python: 2`, `Python: 3`, `Python: 4`] 

列表推導式還可以進行篩選,如下:

list_1 = [num for num in range(20) if num < 5 or num == 15]
print(list_1) 

# 列印內容如下:
[0, 1, 2, 3, 4, 15] 

升級一點,將一個巢狀列表中以”a”開頭和以”h”開頭的元素存放在一個空列表中
基礎寫法如下:

names = [[`abc`, `abb`, `zzz`],["hello","world","xiaoming"]] 
list_names = [] 
for name_1 in names: 
    if type(name_1) == list: 
        for name_2 in name_1: 
            if name_2.startswith("a") or name_2.startswith("h"):
                list_names.append(name_2) 
print(list_names) 

# 列印內容如下:
[`abc`, `abb`, `hello`]         

使用列表推導法

names = [[`abc`, `abb`, `zzz`],["hello","world","xiaoming"]] 
list_names = [name_2 for name_1 in names if type(name_1) for name_2 in name_1 if name_2.startswith("a") or 
name_2.startswith("h")] 

# 列印內容如下:
[`abc`, `abb`, `hello`] 

生成器推導式:
與列表推導式類似,只不過列表是使用[],生成器推導式使用的是()

g_1 = (num for num in range(20)) 
print(g_1) 
print(g_1.__next__()) 
print(g_1.__next__()) 

# 列印內容如下:
<generator object <genexpr> at 0x00000000026A0ED0>
0 
1 

從列印內容和使用__next__()方法可以看出g_1是列表表示式.
可以使用for迴圈列印生成器物件

g_1 = (num for num in range(20)) 
for num in g_1: 
  print(num) 

生成器的篩選與列表推導式用法一樣,只不過是()
如下:過濾1-20內的所有偶數

g_1 = (num for num in range(20) if num % 2 == 0) 

 

升級:與上面列表推導式升級練法類似.

names = [[`abc`, `abb`, `zzz`],["hello","world","xiaoming"]] 
list_names = (name_2 for name_1 in names if type(name_1) for name_2 in name_1 if name_2.startswith("a") or 
name_2.startswith("h")) # 建立生成器物件 
print(list_names) 
for buf in list_names: 
    print(buf) 

# 列印內容下:
<generator object <genexpr> at 0x0000000002150ED0> 
abc 
abb 
hello

 

生成器表示式和列表推導式的區別:

  • 列表推導式比較耗記憶體,一次性載入.生成器表示式幾乎不佔用記憶體.使用的時候才分配和使用記憶體
  • 得到的值不一樣,列表推導式得到的是一個列表.生成器表示式獲取的是一個生成器

字典推導式:

list_1 = ["電視劇","電影"] 
list_2 = ["上海灘","黃飛鴻"] 
dict_1 = {list_1[i]:list_2[i] for i in range(len(list_1))} 
print(dict_1) 

# 列印內容如下:
{`電視劇`: `上海灘`, `電影`: `黃飛鴻`} 

集合推導式:
集合的特點;無序,不重複 所以集合推導式自帶去重功能

list_1 = [1,2,3,4,2,3,5] 
set_1 = {i for i in list_1} # 集合推導式 
print(set_1) 

# 列印內容如下:
{1, 2, 3, 4, 5}

 

總結:

  • 推導式有列表推導式,生成器推導式,字典推導式,集合推導式
  • 生成器表示式: (結果 for 變量 in 可迭代物件 if 條件篩選)
  • 生成器表示式可以直接獲取到生成器物件,生成器物件具有惰性,每次只能列印一個生成器內容,可以使用for迴圈列印生成器所有的內容.

相關文章