python迭代器與生成器詳解

迭代器

print(dir([]))  # 輸出列表所有的方法

什麼叫迭代

字符串、列表、元組、字典、集合都可以被for循環,說明他們都是可迭代的

from collections.abc import Iterable
                             
l = [1,2,3,4]                
t = (1,2,3,4)                
d = {1:2,3:4}                
s = {1,2,3,4}                
                             
print(isinstance(l,Iterable))
print(isinstance(t,Iterable))
print(isinstance(d,Iterable))
print(isinstance(s,Iterable))

能被for循環的就是“可迭代的”,要想可迭代,內部必須有一個__iter__方法。

print([1,2].__iter__())

# 結果 迭代器
<list_iterator object at 0x1024784a8>

迭代器協議

# 列表的迭代器比起列表來說實現了哪些新方法

'''
dir([1,2].__iter__())是列表迭代器中實現的所有方法,dir([1,2])是列表中實現的所有方法,都是以列表的形式返回給我們的,爲了看的更清楚,我們分別把他們轉換成集合,
然後取差集。
'''
#print(dir([1,2].__iter__()))
#print(dir([1,2]))
print(set(dir([1,2].__iter__()))-set(dir([1,2])))

結果:
{'__length_hint__', '__next__', '__setstate__'}
iter_l = [1,2,3,4,5,6].__iter__()
#獲取迭代器中元素的長度
print(iter_l.__length_hint__())
#根據索引值指定從哪裏開始迭代
print('*',iter_l.__setstate__(4))
#一個一個的取值
print('**',iter_l.__next__())
print('***',iter_l.__next__())

在for循環中,就是在內部調用了__next__方法才能取到一個一個的值。

l = [1,2,3,4]
l_iter = l.__iter__()
while True:
    try:
        item = l_iter.__next__()
        print(item)
    except StopIteration:
        break

迭代器遵循迭代器協議:必須擁有__iter__方法和__next__方法。

from collections.abc import Iterable
from collections.abc import Iterator

print(isinstance([],Itrator))
print(isinstance([],Itrable))

總結:

  1. 迭代器協議和可迭代協議
  2. 迭代器遵循迭代器協議:必須擁有__iter__方法和__next__方法。
  3. 可迭代,內部必須有一個__iter__方法。
  4. 只要是迭代器 一定可迭代
  5. 可以被for循環的都是可迭代的
  6. 可迭代的對象.__iter__()方法就可以得到一個迭代器
  7. 迭代器中的__next__()方法可以一個一個的獲取值

生成器

我們自己寫的能實現迭代器功能的東西就叫生成器。

Python中提供的生成器:

  1. **生成器函數:**常規函數定義,但是,使用yield語句而不是return語句返回結果。yield語句一次返回一個結果,在每個結果中間,掛起函數的狀態,以便下次從它離開的地方繼續執行
  2. **生成器表達式:**類似於列表推導式,但是,生成器返回按需產生結果的一個對象,而不是一次構建一個結果列表

生成器Generator:

本質:迭代器(所以自帶了__iter__方法和__next__方法,不需要我們去實現)

特點:惰性運算,開發者自定義

生成器函數

  • 只要含有yield關鍵字的函數都是生成器函數
  • yield不能和return共用且需要寫在函數內
  • 調用函數之後函數不執行,返回一個生成器
  • 每次調用__next__方法的時候會取到一個值,直到取完最後一個,再執行__next__就會報錯
  • 生成器函數:執行之後會得到一個生成器作爲返回值
import time
def genrator_fun1():
    a = 1
    print('現在定義了a變量')
    yield a
    b = 2
    print('現在又定義了b變量')
    yield b

g1 = genrator_fun1()
print('g1 : ',g1)       #打印g1可以發現g1就是一個生成器
print('-'*20)   #我是華麗的分割線
print(next(g1))
time.sleep(5)   #sleep一秒看清執行過程
print(next(g1))

# 運行結果
>>> g1 :  <generator object genrator_fun1 at 0x000002160AEFC4F8>
>>> --------------------
>>> 現在定義了a變量
>>> 1
# 等了5秒
>>> 現在又定義了b變量
>>> 2
def produce():
    """生產衣服"""
    for i in range(2000000):
        yield "生產了第%s件衣服"%i

product_g = produce()
# print(product_g.__next__()) #要一件衣服
# print(product_g.__next__()) #再要一件衣服
# print(product_g.__next__()) #再要一件衣服
num = 0
for i in product_g:         #要一批衣服,比如5件
    print(i)
    num +=1
    if num == 5:
        break


#到這裏我們找工廠拿了8件衣服,我一共讓我的生產函數(也就是produce生成器函數)生產2000000件衣服。
#剩下的還有很多衣服,我們可以一直拿,也可以放着等想拿的時候再拿

生成器監聽文件輸入的例子

import time


def tail(filename):
    f = open(filename)
    f.seek(0, 2) #從文件末尾算起
    while True:
        line = f.readline()  # 讀取文件中新的文本行
        if not line:
            time.sleep(0.1)
            continue
        yield line

tail_g = tail('tmp')
for line in tail_g:
    print(line)

send

def generator():
    print(123)
    content = yield 1
    print('=======',content)
    print(456)
    yield 2

g = generator()
ret = g.__next__()
print('***',ret)
ret = g.send('hello')   #send的效果和next一樣
print('***',ret)

#send 獲取下一個值的效果和next基本一致
#只是在獲取下一個值的時候,給上一yield的位置傳遞一個數據
#使用send的注意事項
    # 第一次使用生成器的時候 是用next獲取下一個值
    # 最後一個yield不能接受外部的值

獲取移動平均值

def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield average
        total += term
        count += 1
        average = total/count


g_avg = averager()
next(g_avg)
print(g_avg.send(10))
print(g_avg.send(30))
print(g_avg.send(5))

預激協程的裝飾器

def init(func):  #在調用被裝飾生成器函數的時候首先用next激活生成器
    def inner(*args,**kwargs):
        g = func(*args,**kwargs) # g = averager()
        next(g)
        return g
    return inner

@init  # averager = init(averager) ===> inner
def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield average
        total += term
        count += 1
        average = total/count


g_avg = averager()   # ===> inner()
# next(g_avg)   在裝飾器中執行了next方法
print(g_avg.send(10))
print(g_avg.send(30))
print(g_avg.send(5))

yield from 一個一個字符返回

def gen1():
    for c in 'AB':
        yield c
    for i in range(3):
        yield i

print(list(gen1()))

>>> ['A', 'B', 0, 1, 2]


def gen2():
    yield from 'AB'
    yield from range(3)

print(list(gen2()))
>>> ['A', 'B', 0, 1, 2]

列表推導式和生成器表達式

egg_list=['雞蛋%s' %i for i in range(10)] #列表解析 列表推導式

#峯哥瞅着alex下的一筐雞蛋,捂住了鼻子,說了句:哥,你還是給我只母雞吧,我自己回家下

laomuji=('雞蛋%s' %i for i in range(10))#生成器表達式
print(laomuji)
print(next(laomuji)) #next本質就是調用__next__
print(laomuji.__next__())
print(next(laomuji))

# 執行結果
>>> <generator object <genexpr> at 0x000002BCD1DDC4F8>
>>> 雞蛋0
>>> 雞蛋1
>>> 雞蛋2


總結:

  1. 把列表推導式的[]換成()得到的就是生成器表達式
  2. 列表解析與生成器表達式都是一種便利的編程方式,只不過生成器表達式更節省內存
  3. Python使用迭代器協議,讓for循環變得更加通用。
  4. 大部分內置函數,也是使用迭代器協議訪問對象的。
# sum函數是Python的內置函數,該函數使用迭代器協議訪問對象,
# 而生成器實現了迭代器協議,所以,我們可以直接這樣計算一系列值的和:

sum(x ** 2 for x in range(4))
# 而不用多此一舉的先構造一個列表:
sum([x ** 2 for x in range(4)]) 

各種推導式

推導式的套路

variable = [out_exp_res for out_exp in input_list if out_exp == 2]
  out_exp_res:  # 列表生成元素表達式,可以是有返回值的函數。
  for out_exp in input_list:  # 迭代input_list將out_exp傳入out_exp_res表達式中。
  if out_exp == 2:  # 根據條件過濾哪些值可以。

列表推導式

例一:30以內所有能被3整除的數

multiples = [i for i in range(30) if i % 3 is 0]
print(multiples)
# Output: [0, 3, 6, 9, 12, 15, 18, 21, 24, 27]

例二:30以內所有能被3整除的數的平方

multiples = [i*i for i in range(30) if i % 3 is 0]
print(multiples)

例三:找到嵌套列表中名字含有兩個‘e’的所有名字

names = [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'],
         ['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry', 'Eva']]

print([name for lst in names for name in lst if name.count('e') >= 2])  # 注意遍歷順序,這是實現的關鍵

字典推導式

例一:將一個字典的key和value對調

mcase = {'a': 10, 'b': 34}
mcase_frequency = {mcase[k]: k for k in mcase}
print(mcase_frequency)

例二:合併大小寫對應的value值,將k統一成小寫

mcase = {'a': 10, 'b': 34, 'A': 7, 'Z': 3}
mcase_frequency = {k.lower(): mcase.get(k.lower(), 0) + mcase.get(k.upper(), 0) for k in mcase.keys()}
print(mcase_frequency)

集合推導式

例:計算列表中每個值的平方,自帶去重功能

squared = {x**2 for x in [1, -1, 2]}
print(squared)
# Output: set([1, 4])

練習題:

# 例1:  過濾掉長度小於3的字符串列表,並將剩下的轉換成大寫字母
[name.upper() for name in names if len(name)>3] 
# 例2:  求(x,y)其中x是0-5之間的偶數,y是0-5之間的奇數組成的元祖列表
[(x,y) for x in range(5) if x%2==0 for y in range(5) if y %2==1] 
# 例3:  求M中3,6,9組成的列表M = [[1,2,3],[4,5,6],[7,8,9]]
[row[2] for row in M]

發佈了30 篇原創文章 · 獲贊 83 · 訪問量 9132
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章