1. 程式人生 > >python迭代器-生成器-列表推導式-生成器表示式-day11-12

python迭代器-生成器-列表推導式-生成器表示式-day11-12

生成器Generator
    生成器函式
一個包含yield關鍵字的函式就是一個生成器函式。yield可以為我們從函式中返回值,但是yield又不同於return,return的執行意味著程式的結束,呼叫生成器函式不會得到返回的具體的值,而是得到一個可迭代的物件。每一次獲取這個可迭代物件的值,就能推動函式的執行,獲取新的返回值。直到函式執行結束
    def func():
        print('hello')
        yield 1
    g = func()      #呼叫生成器函式
    print(g)        #g是一個generator生成器
結果:
    <generator object func at 0x000001F22C0D0EB8>
#呼叫就不會執行這個函式,而是返回一個生成器
    print(dir(g))     #g是一個迭代器,所以所有的生成器都是迭代器
    a = g.__next__()
    print(a)
結果:
    hello
    1

事例二:next本質就是呼叫__next__
    def genrator_fun1():            #1 #5
        a = 1                       #6
        print('現在定義了a變數')       #7
        yield a                     #8 #10
        b = 2                       #11
        print('現在又定義了b變數')  #12
        yield b                     #13
    g1 = genrator_fun1()            #2
    #列印g1可以發現g1就是一個生成器
    print('g1 : ',g1)               #3
    print(next(g1))                 #4
    print(next(g1))                 #9
結果:
    g1 :  <generator object genrator_fun1 at 0x00000195435B0EB8>
    現在定義了a變數
    1
    現在又定義了b變數
    2
yield關鍵字的特點: 可以記錄當前函式中執行的位置,下一次繼續執行
next和yield是一對搭檔 : next開始函式的執行 yield停止函式的執行
生成器函式最大的特點:
呼叫之後不執行,需要next來觸發這個函式繼續向下執行
一個函式中可以有多個yield
    def func():
        yield 1     #停止符,也是一個記錄符(到這裡num1就執行完了返回結果給num1)
        print(123)      #接著上次的程式碼開始執行
        yield 2         #遇到yield又停止了,返回結果給num2
        for i in range(2):
            print(i)
        yield 3
    g = func()
    num1 = g.__next__()
    print(num1)
    num2 = g.__next__() #num1函式執行完後就開始執行num2
    print(num2)
    num3 = g.__next__()
    print(num3)
結果
    1
    123
    2
    0
    1
    3

事例二
    def f():
        print(111)
        yield 1
        print(222)
        yield 3
        print(333)
        yield 5
    f1 = f()
    for i in f1:
        print('*'*i)

    # num1 = next(f1)
    # num2 = next(f1)
    # num3 = next(f1)
    # print(num1,num2,num3)
結果
    111
    *
    222
    ***
    333
    *****

執行效率對比,明顯yield一次取10個數比下邊這個一次生成所有資料,然後再從所有的資料中判斷取值效率要高的多
    def func1():
        for i in range(10000000):
            yield (i+1)%10
    g = func1()
    for i in range(10):
        print(g.__next__())
----------------------------
    def func2():
        new_l = []
        for i in range(10000000):
            new_l.append((i+1)%10)
        return new_l
    ret = func2()
    count = 0
    for i in ret:
        if count < 10:
            print(i)
        else:
            break
        count+= 1
    iter_ret = ret.__iter__()
    for i in range(10):
        iter_ret.__next__()

#做2000001件衣服,前兩次每次輸出1件,後邊一次輸出80件
    def get_cloth():
        for i in range(1,2000001):
            yield '第%s件衣服'%i
    g = get_cloth()
    print(g.__next__())     #這是我已經執行過的第1件衣服
    print(g.__next__())     #這是我已經執行過的第2件衣服
    for i in range(80):
        print(g.__next__()) #下面我開始一次要80件就從3件開始執行82結束

# 監聽檔案的輸入,對於檔案中隨時輸入的內容進行自動化分析/自動化展示
def get_line(f):
    f = open('f',encoding='utf-8')
    while True:
        line = f.readline().strip()
        if not line:
            continue
        yield line
line_g = get_line(file)
for line in line_g:
    print(line.split(','))

總結
可迭代物件:
    擁有__iter__方法
    特點:惰性運算
    例如:range(),str,list,tuple,dict,set

迭代器Iterator:
  擁有__iter__方法和__next__方法
  例如:iter(range()),iter(str),iter(list),iter(tuple),iter(dict),iter(set),reversed(list_o),map(func,list_o),filter(func,list_o),file_o

生成器Generator:
  本質:迭代器,所以擁有__iter__方法和__next__方法
  特點:惰性運算,開發者自定義
使用生成器的優點:
1.延遲計算,一次返回一個結果。不會一次生成所有的結果,這對於大資料量處理,將會非常有用
2.提高程式碼可讀性

send 獲取下一個值的效果和next基本一致
只是在獲取下一個值的時候,給上一yield的位置傳遞一個數據
send使用的注意事項
    第一次使用生成器的時候,是用next獲取下一個值
    最後一個yield不能接受外部的值
    def f():
        print(111)
        # smell = yield 1   #ret把000傳遞給yield位置上即smell=000
        # print(smell)
        yield 1             #ret的yield返回值1
        print(222)
        smell2 = yield 2
        print(smell2)       #這個是最後一個yield不接受外部的引數所以沒有返回值
        yield 3
    g = f()
    print(g.__next__())
    ret = g.send('000')     #傳一個值到生成器裡
    print(ret)
    ret2 = g.send('ccc')
    print(ret2)
結果
    111
    1
    222
    2
    ccc
    3
如果啟用上邊的兩行結果
    111
    1
    000
    1
    222
    2


yield from生成器函式呼叫從中取值?
    def func():
        l1 = [1,2,3]
        s2 = 'abc'
        l1.extend(s2)
        print(l1)
        for i in l1:
            yield i
    g = func()
    for i in g:
        print(i)
結果:
    [1, 2, 3, 'a', 'b', 'c']
    1
    2
    3
    a
    b
    c

事例二
    def func():
        l1 = [1,2,3]
        s2 = 'abc'
        yield from l1   #from迴圈的意思,相當於for
        yield from s2
    g = func()
    for i in g:
        print(i)
執行結果
    1
    2
    3
    a
    b
    c

事例三
    def func():
        l1 = [1,2,3]
        s2 = 'abc'
        for a in l1:
            yield a
        for j in s2:
            yield  j
    g = func()
    for i in g:
        print(i)

事例四
    def gen2():
        yield from 'AB'
        yield from range(3)
    print(list(gen2()))
等同
    def gen1():
        for c in 'AB':
            yield c
        for i in range(3):
            yield i
    print(list(gen1()))


float 浮點數
    1.2
    2.3333344
    2.56789*10**2 = 256.789
    為什麼要叫浮點數 : 因為小數點是浮動的
    浮點數能表示的數學上的數字: 有理數(有限小數 無限迴圈小數)
    浮點數的小數點是不準確的: 小數的小數位是轉換成二進位制儲存
    如果你建立的變數本身帶小數點,那麼這個變數的資料型別直接就是浮點數
    所有的除法(除了//)得到的都是小數
浮點數如果太長請轉換成字串儲存
a = '1.71264864975975073507505'

除法
python2.x 整數除以整數就會取整(向下取整),有一個數浮點數,就按照浮點數計算
python3.x 所有的除(除了//)的結果都是小數

生成器函式中
send/__next__  生成器函式之外用的
yield/yield from 生成器函式之內用的

next+send == yield
如果函式中的yield要接收引數,那麼應該使用send傳值
如果函式中的yield不需要接收引數,那麼應該使用next即可
生成器和迭代器是一樣的,內部的值都只能取一次
從生成器中取值的方式也和迭代器是一樣的:
    for
    next/send
    資料型別的強制轉換(list)


面試題
例題一
def demo():
    for i in range(4):
        yield i
g=demo()
g1=(i for i in g)
g2=(i for i in g1)
print(list(g1))
print(list(g2))  # 生成器中的資料只能取一次,去完就沒有了
結果
[0, 1, 2, 3]
[]

例題二
def demo():
    for i in range(3):
        yield i
g=demo()            #g=0,1,2
g1=(i for i in g)
g2=(i for i in g1)
print(g2.__next__())    #0  #誰先取值就給誰
print(g2.__next__())    #1
print(g1.__next__())    #2,因為g2把前兩個數給打印出來了,所以只剩下2,就給了g1
執行結果
0
1
2

列表推導式和生成器表示式
sum函式是Python的內建函式,該函式使用迭代器協議訪問物件,而生成器實現了迭代器協議,所以可以直接這樣計算一系列值的和
生成器表示式:幾乎不佔記憶體
print(sum(x ** 2 for x in range(10000000)))

#而不用多此一舉的先構造一個列表:記憶體佔用大,機器容易卡死
#print(sum([x ** 2 for x in range(10000000]))

列表推導式
應用場景:當已經有了一個列表,從這個列表中的每一個元素都需要做某個操作,並且需要將操作的結果放在一個新的列表中,適合使用列表推導式
新的列表 = [每一個元素要做的操作 for 列表中的每一個元素 in 列表]

迴圈/列表轉換成列表推導式
例題一
    l = [1,2,3,4,5,6]
    new_l = []
    for i in l:
        new_l.append(i*i)   #[1,4,9,16,25,36]
    print(new_l)
#列表推導式
    new_l2 = [i*i for i in l]   
    print(new_l2)
等同
    l = [1,2,3,4,5,6]
    print([i*i for i in l])

例題二
    new_lst = []
    for i in range(2,20):
        new_lst.append(i//3)
    print(new_lst)
列表推導式
    lst = [i//3 for i in range(2,20)]
    print(lst)

例題三
#請計算0-100內能被3整除的所有數字
    new_lst = []
    for i in range(0,10):
        if i%3 == 0:
            new_lst.append(i)
    print(new_lst)
列表推導式
    print([i for i in range(0,10) if i%3 == 0])


生成器表示式(的結果是生成器):
    處理比較簡單的邏輯
    並且能夠以更少的程式碼來節省時間空間
def func():
    for i in range(101):
        if i%3 == 0:
            yield i
g1 = func()
print(g1.__next__())

生成器表示式
    g = (i for i in range(0,101) if i%3 == 0)
    print(g.__next__())
    print(g.__next__())

#列表推導式
    egg_list=['雞蛋%s' %i for i in range(10)]
    print(egg_list)
生成器表示式
    egg_list=('雞蛋%s' %i for i in range(10))
    print(egg_list)
    print(next(egg_list))



# 計算移動平均值的例子(沒懂)
#def cal_avg():
#    sum_n = 0
#    count = 0
#    while True:
#        if count:
#            num = yield sum_n/count   # 7/1 num = 9  16/2 = 8  num = 8  24/4 = 8
#        else:
#            num = yield 0    # num = 7
#        sum_n += num         # sum_n = 7 + 9 = 16 +8 = 24 +10
#        count += 1           # count = 1 + 1 = 2 + 1 = 3 + 1
## 7,9,8,10
#g = cal_avg()
#g.__next__()   # 0
#avg1 = g.send(7)
#print(avg1)
#avg2 = g.send(9)
#print(avg2)
#avg3 = g.send(8)
#avg3 = g.send(10)


#def add(n,i):
#    return n+i
#def test():
#    for i in range(4):
#        yield i
#g=test()           #g= 0,1,2,3
#for n in [1,10]:   #n=1,10
#    g=(add(n,i) for i in g) #g= 0,1,2,3所以i=0,1,2,3
#print(list(g))     #因為add=n+i,所以迴圈第一次就等於0+10,第二次就等於1+10
執行結果
[20, 21, 22, 23]