萬惡之源 - Python生成器


生成器

首先我們來看看什么是個生成器,生成器本質就是迭代器

在python中有三種方式來獲取生成器

  1.通過生成器函數

  2.通過各種推到式來實現生成器

  3.通過數據的轉換也可以獲取生成器

首先,我們先看一個很簡單的函數:

def func():

    print(11)
    return 22

ret = func()
print(ret)

# 運行結果:
11
22

將函數中的return換成yield就是生成器

# 函數
def func():

    print('這是函數func')

    return '函數func'

func()

# 生成器
def func1():

    print('這是函數func1')

    yield '函數func'

func1()

運行的結果和上面的不一樣,為什么呢?? 由於函數中存在yield,那么這個函數就是一個生成器函數.

def func1():

    print('這是函數func1')

    yield '函數func'

print(func1())

結果:<generator object func1 at 0x0000023B3F280B48>

我們在執行這個函數的時候.就不再是函數的執行了.而是獲取這個生成器.如何使用???

想想迭代器,生成器的本質就是迭代器.所以我們可以直接執行__next__()來執行以下生成器

def func():
     print("111")
     yield 222
gener = func() # 這個時候函數不會執⾏. ⽽是獲取到⽣成器
ret = gener.__next__() # 這個時候函數才會執⾏. yield的作⽤和return⼀樣. 也是返回數據
print(ret)
結果:
111
222

那么我們可以看到,yield和return的效果是一樣的,但是還是有點區別

  yield是分段來執行一個函數

  return是直接停止這個函數

def func():
    print("111")
    yield 222
    print("333")
    yield 444


gener = func()
ret = gener.__next__()
print(ret)
ret2 = gener.__next__()
print(ret2)
ret3 = gener.__next__()
# 最后⼀個yield執⾏完畢. 再次__next__()程序報錯
print(ret3)

結果:
111
222
333
444

當程序運行完最后一個yield,那么后面繼續運行__next__()程序會報錯

好了生成器我們說完了.生成器有什么作用呢?

我們來看一下這個需求,老男孩向樓下賣包子的老板訂購了10000個包子.包子鋪老板實在一下就全部都做出來了  

def eat():

    lst = []

    for i in range(1,10000):
        lst.append('包子'+str(i))
    return lst

e = eat()
print(e)

這樣做是沒有問題但是我們目前這么點人吃不完這么多,只能先放到一個地方,要是能夠我吃一個老板做一個就完美了.

def eat():
    for i in range(1,10000):
        yield '包子'+str(i)
e = eat()
print(e.__next__())
print(e.__next__())
print(e.__next__())
print(e.__next__())
print(e.__next__())
print(e.__next__())

上下的區別: 第一種是直接把包子都拿來,很占內存也就是很占咱們的位置,第二種使用生成器,想吃就拿一個.吃多少個包多少個.生成器是一個一個的,一直向下進行,不能向上.__next__()到哪,指針就指到哪兒.下一次繼續就獲取指針指向的值

接下來我們再來認識一個新的東西,send方法

send和__next__()一樣都可以讓生成器執行到下一個yield

def eat():
    for i in range(1,10000):
        a = yield '包子'+str(i)
        print('a is',a)

        b = yield '窩窩頭'
        print('b is', b)
e = eat()
print(e.__next__())
print(e.send('大蔥'))
print(e.send('大蒜'))

send和__next__()區別:

send 和 next()都是讓生成器向下走一次

send可以給上一個yield的位置傳遞值,不能給最后一個yield發送值,在第一次執行生成器的時候不能使用send()

第一次調用的時候使用send()也可以但是send的參數必須是None

def func1():
    print('這是函數func1')
    f1 = yield '你好'
    print(f1)
    f2 = yield '我好'
    print(f2)
f = func1()
f.__next__()
f.send('大家好')

生成器可以for循環來循環獲取內部元素:

def func():

    yield 1
    yield 2
    yield 3
    yield 4
    yield 5

f = func()
for i in f:
    print(i)

yield from

在python3中提供一種可以直接把可迭代對象中的每一個數據作為生成器的結果進行返回

def func():
    lst = ['衛龍','老冰棍','北冰洋','牛羊配']
    yield from lst
g = func()
for i in g:
    print(i)

有個小坑,yield from 是將列表中的每一個元素返回,所以 如果寫兩個yield from 並不會產生交替的效果

def func():
    lst1 = ['衛龍','老冰棍','北冰洋','牛羊配']
    lst2 = ['饅頭','花卷','豆包','大餅']
    yield from lst1
    yield from lst2
    
g = func()
for i in g:
    print(i)

推導式

列表推導式

列表推導式生成器表達式以及其他推導式,首先我們先看一下這樣的代碼,給出一個列表,通過循環,想列表中添加1~10:

li = []
for i in range(10):
    li.append(i)

print(li)

我們換成列表推導式是什么樣的,來看看:

列表推導式的常⽤寫法:  

[結果 for 變量 in 可迭代對象] 

ls = [i for i in range(10)]
print(ls)

列表推導式是通過⼀行來構建你要的列表, 列表推導式看起來代碼簡單. 但是出現錯誤之  

后很難排查.   

例. 從python1期到python17期寫入列表lst:

lst = ['python%s' % i for i in range(1,18)]
print(lst)

篩選模式

[結果 for 變量 in 可迭代對象 if 條件]

print([i for i in range(10) if i > 3])
結果:
[4, 5, 6, 7, 8, 9]

生成器表達式  

這個其實就將列表推導式倆邊的中括號換成小括號就可以了,我們來看一下

l = (i for i in range(10))
print(l)  

print(l.__next__())
print(l.__next__())
print(l.__next__())
print(l.__next__())
print(l.__next__())

print(l)的時候獲取到是:

<generator object <genexpr> at 0x000001D8C7570B48>
0
1
2
3
4

生成器表達式也可以進行篩選

# 獲取1-100內能被3整除的數
gen = (i for i in range(1,100) if i % 3 == 0)
for num in gen:
    print(num)


# 100以內能被3整除的數的平⽅
gen = (i * i for i in range(100) if i % 3 == 0)
for num in gen:
    print(num)


# 尋找名字中帶有兩個e的人的名字
names = [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'],
         ['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry', 'Eva']]


# 不用推導式和表達式
result = []
for first in names:
    for name in first:
        if name.count("e") >= 2:
            result.append(name)
print(result)

# 推導式
gen = (name for first in names for name in first if name.count('e') >= 2)

for i in gen:
    print(i)

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

1. 列表推導式比較耗內存,一次性加載.生成器表達式幾乎不占用內存.使用的時候才分配和使用內存

2. 得到的值不一樣,列表推導式得到的是一個列表.生成器表達式獲取的是一個生成器

舉個例子:

李大錘想吃雞蛋就上街買了一籃子的雞蛋放家里,吃的時候拿一個吃的時候拿一個,這樣就是一個列表推導式,一次性拿夠占地方.

王二麻子也想吃雞蛋,他上街卻買了一只母雞回家.等他想吃的時候就讓母雞給下雞蛋,這樣就是一個生成器.需要就給你下雞蛋

生成器的惰性機制: 生成器只有在訪問的時候才取值,說白了.你找他要才給你值.不找他要.他是不會執行的.

def func():
    print(111)
    yield 222
g = func()  # 生成器g

g1 = (i for i in g) # 生成器g1. 但是g1的數據來源於g

g2 = (i for i in g1)    # 生成器g2. 來源g1

print(list(g))   # 獲取g中的數據. 這時func()才會被執行. 打印111.獲取到222. g完畢.
print(list(g1))  # 獲取g1中的數據. g1的數據來源是g. 但是g已經取完了. g1 也就沒有數據了
print(list(g2))  # 和g1同理理


print(next(g))
print(next(g1))
print(next(g2))   # 可以用next來驗證  其實list就是將內容迭代了轉換成了列表 

這是坑,一定要注意,生成器是要值的時候才能拿值,不然就沒有啦

字典推導式

根據名字應該也能猜到,推到出來的是字典

lst1 = ['jay','jj','meet']
lst2 = ['周傑倫','林俊傑','郭寶元']
dic = {lst1[i]:lst2[i] for i in range(len(lst1))}
print(dic)

集合推導式

集合推導式可以幫我們直接生成一個集合,集合的特點;無序,不重復 所以集合推導式自帶去重功能

lst = [1,2,3,-1,-3,-7,9]
s = {abs(i) for i in lst}
print(s)

總結:

    推導式有, 列表推導式, 字典推導式, 集合推導式, 沒有元組推導式    

    生成器表達式: (結果 for 變量量 in 可迭代對象 if 條件篩選)    

    生成器表達式可以直接獲取到⽣成器對象. ⽣成器對象可以直接進行for循環. ⽣成器具有惰性機制. 

一個面試題,難度系數99999999課星

def add(a, b):
    return a + b
def test():
    for r_i in range(4):
        yield r_i
g = test()
for n in [2, 10]:
    g = (add(n, i) for i in g)
print(list(g))

友情提示: 惰性機制,不到最后不會拿值

這個題先讀一下,然后自己分析一下,在用機器運行一下

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM