生成器和各類推導式

昨日回顧

1. 函數名 -> 第一類對象
        函數名就是變量名.
        函數能夠賦值
        函數能夠做爲集合類的元素
        函數能夠做爲參數傳遞
        函數能夠做爲返回值返回
    2. 閉包
        語法: 內層函數對外層函數的局部變量的使用
        def wrapper():
            name = ""
            def inner():
                return name
            return inner

        如何查看一個函數是不是閉包
        函數名.__closure__  有值就是閉包. None就不是閉包

        優勢:
            1. 保護變量不被侵害 (javascript)
            2. 可讓一個變量常駐內存

    3. 迭代器
        在數據中包含了__iter__是一個可迭代對象.
        for循環內部

        it = lst.__iter__()
        while 1:
            try:
                it.__next__()
            except StopIteration:
                break

        特色:
            1. 節省內存
            2. 惰性機制
            3. 只能向前, 不能反覆

        意義: 統一數據類型的遍歷工做

        官方查看xxx是迭代器, 可迭代對象
        from collections import Iterable, Iterator

        isinstance(對象, Iterable)
        isinstance(對象, Iterator)

        迭代器必定可迭代   ->   for循環
        可迭代的不必定是迭代器
View Code

 

今日內容

1. 生成器
        本質就是迭代器.
        一個一個的建立對象
        建立生成器的方式:
            1. 生成器函數
            2. 經過生成器表達式來獲取生成器
            3. 類型轉換(看不到)
    2. 生成器函數 (重點)
        生成器函數中包含 yield , 返回數據和return差很少.
        return會當即結束這個函數的執行
        yield 能夠分段的執行一個函數

        生成器函數在執行的時候返回生成器. 而不是直接執行此函數

        能向下執行的兩個條件:
            __next__(), 執行到下一個yield
            send(), 執行到下一個yield, 給上一個yield位置傳值

        全部的生成器都是迭代器均可以直接使用for循環
        均可以使用list()函數來獲取到生成器內全部的數據

        生成器(##每次調用生成器函數就建立了一個生成器)中記錄的是代碼而不是函數的運行
        def func():
            print("個人天哪 ")
            yield "寶寶"

        △(理解記憶) gen = func() # 建立生成器.  此時運行會把生成器函數中的代碼記錄在內存,當執行到__next__(),
         運行此空間中的代碼, 運行到yield結束.

        優勢: 節省內存, 生成器自己就是代碼. 幾乎不佔用內存
        特色: 惰性機制, 只能向前. 不能反覆

    3. 各類推導式 (詭異)
        列表推導式  [結果 for循環 if]
        字典推導式 {結果(k:v) for循環 if}
        集合推導式 {結果(k) for循環 if}



    4. 生成器表達式 (重點)
        (結果 for循環 if)
        老師講的所謂的與生成器函數相比  生成器表達式是一次性的.是指生成器函數經過gen = func()建立生成器 以後能夠反覆的調用生成器,
而生成器表達式一次執行完以後,下次執行還要從新寫一次.

一.生成器

什麼是生成器.生成器實質就是迭代器.
在python中有三種方式來獲取生成器:
  1.經過生成器函數
  2.經過各類推導式來實現生成器
  3.經過數據的轉換也能夠獲取生成器javascript


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

def func():
    print("111")
    return 222

ret = func()
print(ret)

結果:
111
222

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

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

ret = func()
print(ret)

結果:
<generator object func at 0x10567ff68>

運行的結果和_上面不同. 爲何呢.因爲函數中存在了yield.那麼這個函數就是一個生成器函數.這個時候.咱們再執行這個函數的時候.就再也不是函數的執行了.而是獲取這個生成器.如何使用呢?想一想迭代器.生成器的本質是迭代器.因此.咱們能夠直接執行__next__()來執行如下生成器.面試

def func():
    printC111")
    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__()程序報錯, 也就是說,和return無關了
print(ret3)

結果:
111
Traceback (most recent call last):
222
333
File "/Users/sylar/PycharmProjects/oldboy/iterator.py", line 55, in <module>
444
ret3 = gener.__ .next__ .(0) # 最後一個yield執行完畢,再次__next__()程序報錯, 也就是說,和return無關了
StopIteration

當程序運行完最後一個yield.那麼後面繼續進行__next__()程序會報錯.
好了生成器說完了.生成器有什麼做用呢?咱們來看這樣一個需求. 老男孩向JACK JONES訂購10000套學生服. JACK JONES就比較實在.直接造出來10000套衣服.app

def cloth():
    lst =[]
    for i in range(0, 10000):
        lst. append("衣服" +str(i))
    return lst

cl = cloth()    

可是呢,問題來了.老男孩如今沒有這麼多學生啊.一次性給我這麼多.我往哪裏放啊.很尷尬啊.最好的效果是什麼樣呢?我要1套.你給我1套.一共1000套.是否是最完美的ide

def cloth():
    for i in range(0, 10000):
        yield 「衣服"+str(i)

cl = cloth()
print(cl.__next__())
print(cl.__next__())
print(cl.__next__())
print(cl.__next__())

區別:第一種是直接一次性所有拿出來,會很佔用內存.第二種使用生成器,-次就-個,用多少生成多少.生成器是一個一個的指向下一個.不會回去,__next__()到哪, 指針就指到哪兒.下一次繼續獲取指針指向的值

接下來咱們來看send方法, send和__next__()同樣均可以讓生成器執行到下一個yield.函數

def eat():
    print("我吃什麼啊")
    a = yield "饅頭"
    print("a=",a)
    b = yield 「大餅」
    print("b=",b)
    c = yield 「韭菜盒子」
    print("c=",c)
    yield "GAME OVER"

gen = eat()#獲取生成器
ret1 = gen.__next__()
print(ret1)
ret2 = gen. send("胡辣湯")
print(ret2)
ret3 = gen. send("狗糧")
print(ret3)
ret4 = gen. send("貓糧")
print(ret4)

send和__next__()區別:
  1. send和next()都是讓生成器向下走-次
  2. send可 以給上一個yield的位置傳遞值,不能給最後-個yield發送值. 在第一次執行生成器代碼的時候不能使用send()spa


生成器可使用for循環來循環獲取內部的元素:指針

def func():
    print(111)
    yield 222
    print(333)
    yield 444
    print(555)
    yield 666

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


結果:
111
222
333
444
555
666

二.列表推導式,生成器表達式以及其餘推導式

列表推導式

首先咱們先看一下這樣的代碼,給出一個列表,經過循環,向列表中添加1-14 :

lst=[]
for i in range(1,15):
    lst . append(i)
print(lst)

替換成列表推導式:

lst = [i for i in range(1, 15)]
print(lst)

列表推導式是經過-行來構建你要的列表,列表推導式看起來代碼簡單.可是出現錯誤以後很難排查.

列表推導式的經常使用寫法:
  [結果 for 變量 in 可迭代對象]


例.從python1期到python14期寫入列表Ist:

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

咱們還能夠對列表中的數據進行篩選

篩選模式:
  [結果 for 變量 in 可迭代對象 if條件]

#獲取1-100內全部的偶數
lst = [i for i in range(1, 100)  if i % 2 == 0]
print(lst)

生成器表達式

生成器表達式和列表推導式的語法基本上是同樣的.只是把[]替換成()

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

結果:
<generator object <genexpr> at 0x106768f10>

打印的結果就是一個生成器.咱們可使用for循環來循環這個生成器:

gen = ("麻花藤我第%s次愛你」% i for i in range(10))
for i in gen:
    print(i)

生成器表達式也能夠進行篩選:

#獲取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 name in gen:
    print(name)

生成器表達式和列表推導式的區別:
  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同理

深坑==>生成器.要值得時候纔拿值

字典推導式:

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

#把字典中的key和value互換
dic = {'a': 1, 'b': '2'}
new_ dic = {dic[key]: key for key in dic}
print(new_ dic)

#在如下list中,從lst1中獲取的數據和lst2中相對應的位置的數據組成一個新字典
lst1 = ['jay', 'jj''sylar']
lst2 = ['周杰倫",'林俊杰’, '邱彥濤']
dic = {lst1[i]: lst2[訂] for i in range(len(lst1))}
print(dic)

 

集合推導式:

集合推導式能夠幫咱們直接生成一個集合. 集合的特色:無序,不重複.因此集合推導式自帶去重功能

lst = [1, -1, 8, -8,12]
#絕對值去重
s = {abs(i) for i in lst}
print(s)

 

總結:推導式有,列表推導式,字典推導式,集合推導式,沒有元組推導式
  生成器表達式: (結果 for 變量 in 可迭代對象 if條件篩選)
  生成器表達式能夠直接獲取到生成器對象.生成器對象能夠直接進行for循環.生成器具備惰性機制.

經典面試題

一個面試題.難度係數0000000顆星:

def add(a, b): 
    return a + b

def test():
    for i in range(4):
        yield i


g = test()

for n in [2, 10]:
    g=(add(n,i) for i in g)

print(list(g))

友情提示:惰性機制,不到最後不會拿值
這個題要先讀一下. 而後本身分析出結果.最後用機器跑一下

 

# 生成器經典題
def add(a, b): # 相加
    return a + b

def test(): # 生成器函數 0  1   2  3
    for i in range(4):
        yield i

g = test()  # 建立生成器

for n in [2, 10]:
    g = (add(n, i) for i in g)

print(list(g))
# 結果:[20,21,22,23]
# 解題思路:惰性機制(#有人要值才執行);生成器記錄的是代碼(#只有在執行的時候纔會帶入變量的值)
# 1,拆分for循環
n = 2
g = (add(n, i) for i in g)
n = 10
g = (add(n, i) for i in g)
# 2,將g = test()代入,並層層帶入
n = 2
g = (add(n, i) for i in test())
n = 10
g = (add(n, i) for i in (add(n, i) for i in test()))
# 3,拿值的時候帶入變量的值去執行
print(list(g))  # 拿值list==>for==>__next__()
# 此時的
g = (add(n, i) for i in (add(n, i) for i in test()))
# 代入變量的值
# n = 10
# test()的取值範圍是[0,1,2,3]
# 因此
# list(g) == [(add(10, i) for i in (add(10, i) for i in [0,1,2,3]))]==10*2+[0,1,2,3]
#         == [(add(10, i) for i in [10,11,12,13]]
#         == [20,21,22,23]


# 小結規律:
# test()的取值範圍是初始範圍
# 要拿值時候的n的值是要帶入的值
# for循環決定循環的次數,循環幾回就累加幾回,也就是要加幾個n的值


# 改變1
def add(a, b): # 相加
    return a + b

def test(): # 生成器函數 0  1   2  3
    for i in range(4):
        yield i

g = test()  # 建立生成器

for n in [2, 10]:
    g = (add(n, i) for i in g)
n = 5

print(list(g))
# 結果:[10,11,12,13]
# 解題思路同上,最終拿到
# g = (add(n, i) for i in (add(n, i) for i in test()))
# 而此時n=5,帶入獲得 5*2+[0,1,2,3]=[10,11,12,13]



# 改變2
def add(a, b): # 相加
    return a + b

def test(): # 生成器函數 0  1   2  3
    for i in range(4):
        yield i

g = test()  # 建立生成器

for n in [2, 10, 5]:
    g = (add(n, i) for i in g)


print(list(g))

# 解題思路同上,最終拿到
n = 2
g = (add(n, i) for i in g)
n = 10
g = (add(n, i) for i in g)
n = 5
g = (add(n, i) for i in g)


# 層層代入獲得:
n = 2
g = (add(n, i) for i in test())
n = 10
g = (add(n, i) for i in (add(n, i) for i in test()))
n = 5
g = (add(n, i) for i in (add(n, i) for i in (add(n, i) for i in test())))
# 代入數據獲得:
list(g) =  (add(5, i) for i in (add(5, i) for i in (add(5, i) for i in [0,1,2,3])))=5*3+[0,1,2,3]=[15,16,17,18]

 

區別生成器表達式和列表生成式:

def add(a, b):
    return a + b


def test():
    # print("123")
    for i in range(4):#[0,1,2,3]
        yield i

g = test()

for n in [2, 10, 5]:
    # 第一種:生成器表達式
    # g = (add(n, i) for i in g)  # [15, 16, 17, 18]在生成器表達式內部的生成器或者生成器表達式是不會執行的,直到外部的生成器有人找它要值
    # 第二種:列表生成式,是會直接帶入執行的
    g = [add(n, i) for i in g]  # [17, 18, 19, 20]
#第一種:
# 對for循環進行等價替換,所有代入n=5
# g1 = (add(5, i) for i in g)  #g = test()= [0,1,2,3]
# g2 = (add(5, i) for i in g1)
# g3 = (add(5, i) for i in g2)
#第二種:
# 對for循環進行等價替換,分別代入n=2,10,5
# g1 = (add(2, i) for i in g)  #g = test()= [0,1,2,3]
# g2 = (add(10, i) for i in g1)
# g3 = (add(5, i) for i in g2)



print(list(g))

 

 

 

 

今日做業

相關文章
相關標籤/搜索