1. 程式人生 > >生成器、列表推導式和生成器表示式

生成器、列表推導式和生成器表示式

生成器

生成器特點:

1. 生成器本質上迭代器

2. 生成器是自己用程式碼建立的

獲取生成器的兩種方式:

1. 生成器函式

2. 生成器表示式

 

yield

下面來看一個例子

 1 def func():
 2     print(111)
 3     yield 666
 4     print(222)
 5     yield 233333
 6 
 7 
 8 ret = func()    # 獲取生成器
 9 print(ret)      # <generator object func at 0x000002B1E535B258>
10 print
(type(ret)) # <class 'generator'>

把函式的return換成yield就可以把函式變成生成器,變成生成器後再讀到函式名加括號後就不再表示執行函式而是表示獲取一個生成器。變成生成器後就可以用next來取值了,來看程式碼

print(next(ret))   # 一個next執行一個yield(和yield之前的)

執行結果

111
666

再繼續取值

1 print(next(ret))   # 111 666, 一個next執行一個yield(和yield之前的)
2 print(next(ret))   #
222 233333 3 print(next(ret)) # 超出,報錯

 

生成器函式牢記一點:一個next只執行一個yield語句和此yield語句與上一個yield語句之間的語句

如果將yield語句賦值給某個變數會怎樣,來看程式碼

 1 def func():
 2     print(111)
 3     a = yield 666
 4     print("a1:", a)
 5     print(222)
 6     yield 888
 7     print("a2:", a)
 8     yield 999
 9 
10 
11
ret = func()

執行一個next

print(next(ret))

執行結果

111
666

執行兩個next

111
666
a1: None
222
888

執行三個next

print(next(ret))   
print(next(ret))
print(next(ret))

結果

111
666
a1: None
222
888
a2: None
999

分析上述程式碼我們可以得出規律:

1. yield語句是將yield後面的值返回給next()語句

2. yield語句本身沒有返回值

3. 每執行一個next語句就從上一個yield語句(不包括)開始從上往下執行

 

yield與return的異同:

1. return會終止函式,yield不會終止函式

2. yield和return都有返回值,yield給next()返回值,return給函式名()返回值

 

send

生成器函式還有一個方法:send(),send與next類似,也可以一個一個地取值,但是send還有個作用,可以傳值給上一個yield(括號裡寫None就跟next一樣)

 

 1 def func():
 2     a = yield 111
 3     print(a)
 4     print("mandy")
 5     b = yield 222
 6     print(b)
 7     print("sa")
 8     yield 333
 9     yield "hanser"
10 
11 
12 gen = func()   # 獲取生成器
13 # sen不僅能對應yield取值,而且可以給上一個yield語句傳送一個值
14 
15 print(gen.send(None))   # 第一個send不能傳值(傳值為None,只取值)    111
16 print(gen.send(666))   # 兩個作用:1.把666傳給a;2.取值222.    666  mandy  222
17 print(gen.send("yousa"))                               # yousa  sa  333
18 print(next(gen))                                       # hanser

 

執行結果

111
666
mandy
222
yousa
sa
333
hanser

注意send(value)是將value傳給上一個yield的yield  值這個語句,如果函式裡有將yield  值賦值給某個變數,就要注意變數值的變化。程式碼的執行順序是從上一個yield的後面開始,從上往下執行,最後執行當前的yield(next)。還有兩點點需要注意:

1. 第一次取值不能用send傳參

2. 最後一個yield永遠得不到send傳的值

 

yield from

前面的yield的返回值都是數字,如果yield的返回值是可迭代物件會有什麼不同呢,一起來看

 

1 def func():
2     lst = [1, 2, 3, 4]
3     yield lst
4 
5 
6 gen = func()     # gen [[1, 2, 3, 4]]
7 print(gen)

 

執行結果

<generator object func at 0x0000016A300AFF68>
[1, 2, 3, 4]

用next得到的結果是一個列表,如果不用next取值,直接用for迴圈取值,能取到嗎?來看

for i in next(gen):
    print(i)

結果

<generator object func at 0x0000024B4905FF68>
1
2
3
4

由此可以可以知道gen這個生成器類似於一個列表,裡面只有一個元素也是列表,即 [[1, 2, 3, 4]],

如果把yield換成yield from,結果會是怎樣的呢

1 def func():
2     lst = [1, 2, 3, 4]
3     yield from lst        # 把lst變成迭代器
4 
5 
6 gen = func()
7 print(next(gen))
8 print(next(gen))
9 print(next(gen))

執行結果

1
2
3

 

 

列表推導式

定義:列表推導式就是用一行程式碼構建簡單的列表

列表推導式的三種模式

1. 迴圈模式

語法:[變數(加工後的變數) for 變數 in iterable]

[i for i in range(10)]

2. 篩選模式

語法:[變數(加工後的變數) for 變數 in iterable if 條件]

[i for i in range(10) if i % 2 == 0]
["地球%s號" % i for i in range(1, 50) if i % 3 == 0]

3. 三元模式(迴圈模式與三元運算的結合)

lst = ["$" if i % 2 == 0 else i for i in [i**2 for i in range(10)]]
print(lst)

執行結果

['$', 1, '$', 9, '$', 25, '$', 49, '$', 81]

優點:節省程式碼

缺點:(1)只有一行,debug查不出來

   (2)不能構建複雜的列表(常規的方法可以)

生成器表示式

生成器表示式語法與列表推導式幾乎一樣,只是把[]換成()

1 gen = ("嫦娥%s號" % i for i in range(1, 10))
2 print(gen)
3 print(next(gen))  # 嫦娥1號
4 for i in gen:
5     print(i)     # 嫦娥2號 嫦娥3號...

執行結果

<generator object <genexpr> at 0x0000016C3625FF68>
嫦娥1號
嫦娥2號
嫦娥3號
嫦娥4號
嫦娥5號
嫦娥6號
嫦娥7號
嫦娥8號
嫦娥9號

什麼時候用列表推導式,什麼時候用生成器表示式?

資料量小的時候用列表推導式,資料量大的時候用生成器表示式,因為生成器表示式可以一個一個地取值,不佔記憶體

 

字典推導式和集合推導式

字典推導式

字典推導式的語法和列表推導式類似,只是把變數換成鍵值對,[]換成{}

print({i:None for i in range(1, 10)})

結果

{1: None, 2: None, 3: None, 4: None, 5: None, 6: None, 7: None, 8: None, 9: None}

鍵值交換

# 鍵值交換
m = {"a": 10, "b": 34, "c": 88}
print({v: k for k, v in m.items()})

執行結果

{10: 'a', 34: 'b', 88: 'c'}

集合推導式

set2 = {i*3+1 for i in range(10)}
print(set2)

執行結果

set2 = {(i*3+1) % 4 for i in range(10)}
print(set2)

執行結果

{0, 1, 2, 3}