迭代器和生成器


 迭代器

可迭代的數據類型

可迭代的:凡是可以被for循環的都是可迭代的

例如:list    dic    str     set     tuple     range()     enumerate(枚舉)     f=open()(文件句柄)

查看數據類型的所有方法

dir(數據類型)

print(dir([]))

print(dir({}))

可迭代協議:

只要含有__iter__()方法的就是可迭代

凡是可以被for循環的數據類型就一定含有__iter__()方法

迭代器協議和可迭代對象

1.迭代器協議是指:對象必須提供一個next方法,執行該方法要么返回迭代中的下一項,要么就引起一個StopIteration異常,以終止迭代 (只能往后走不能往前退)

2.可迭代對象:實現了迭代器協議的對象(如何實現:對象內部定義一個__iter__()方法)

3.協議是一種約定,可迭代對象實現了迭代器協議,python的內部工具(如for循環,sum,min,max函數等)使用迭代器協議訪問對象。

判斷一個數據類型是否是迭代器和可迭代對象:

from collections import Iterable
from collections import Iterator
print(isinstance([],Iterator))#True     如果值為True就是迭代器
print(isinstance([],Iterable))#True      判斷是否為可迭代對象如果值為True就是可迭代對象

迭代器協議的原理

1 #基於迭代器協議
 2 li = [1,2,3]
 3 diedai_l = li.__iter__()
 4 print(diedai_l.__next__())
 5 print(diedai_l.__next__())
 6 print(diedai_l.__next__())
 7 # print(diedai_l.__next__())  # 超出邊界報錯
 8 
 9 #下標
10 print(li[0])
11 print(li[1])
12 print(li[2])
13 # print(li[3]) # 超出邊境報錯
14 
15 # 用while循環模擬for循環機制
16 diedai_l = li.__iter__()
17 while True:
18     try:
19         print(diedai_l.__next__())
20     except StopIteration:
21         print("迭代完畢,循環終止")
22         break
23 
24 # for循環訪問方式
25 # for循環本質就是遵循迭代器協議的訪問方式,先調用diedai_l=li.__iter__方法
26 # 或者直接diedai_l=iter(l),然后依次執行diedai_l.__next__(),直到捕捉到
27 # StopItearation終止循環
28 # for循環所有的對象的本質都是一樣的原理
迭代器協議的原理方法
#序列類型      字符串,列表,元組都有下標,你用上述的方式訪問
#非序列類型   字典,集合,文件
   
#for循環就是基於迭代器協議提供了一個統一的可以遍歷所有對象的方法,
#即在遍歷之前,先調用對象的__iter__方法將其轉換成一個迭代器,
#然后使用迭代器協議去實現循環訪問,這樣所有的對象就都可以通過for循環來遍歷了,
#而且你看到的效果也確實如此,這就是無所不能的for循環,覺悟吧,年輕人

迭代器

同時含有__iter__()方法和__next()__方法的就是迭代器

示例詳解

s = [1,2,3,4,5]
# 判斷s是迭代器還是可迭代對象

from collections import Iterator
from collections import Iterable

print(isinstance(s,Iterable))  #True  是可迭代對象,有__iter()__方法
print(s.__iter__())    #對應的內存地址
print(isinstance(s,Iterator))  #False 不是迭代器 沒有__next__()方法

res = s.__iter__()

print(isinstance(res,Iterator))  #True 是迭代器 既有__iter()__又有__next()__方法
print(res.__next__())  #  __next__方法一次只能取一個值,想要取多個值必須執行多次此方法
print(res.__next__())
print(res.__next__())
print(res.__next__())
print(res.__next__())
print(res.__next__())  #如果執行的__next()__方法次數超出了s本身的長度后會報錯
#StopIteration,因為s本身的長度只有5,當執行到第六次__next()__方法的時候就會出現這個錯誤

為啥要用迭代器

#優點
# 1:迭代器提供了一種不依賴於索引的取值方式,這樣就可以遍歷那些沒有索引的可迭代對象了(字典,集合,文件),一個迭代器只能取所有數據一次
#
2:迭代器與列表比較,迭代器是惰性計算的,更節省內存 #缺點: # 1:無法獲取迭代器的長度,使用不如列表索引取值靈活 # 2:一次性的,只能往后取值,不能倒着取值

迭代器的用途

for循環 

生成器

什么是生成器

生成器的本質上就是函數  只不過是我們自己寫的函數,生成器一定是迭代器  迭代器不一定是生成器

只要函數內部含有yield關鍵字的就是生成器函數   生成器函數執行之后返回一個生成器作為返回值

可以理解為一種數據類型,這種數據類型自動實現了迭代器協議(其他的數據類型需要調用自己內置的__iter__方法),所以生成器就是可迭代對象

生成器的分類及在python中的表現形式

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

為何要使用生成器(生成器的優點)

Python使用生成器對延遲操作提供了支持。所謂延遲操作,是指在需要的時候才產生結果,而不是立即產生結果。

這也是生成器的主要好處(通常所說的惰性運算:只有在需要的時候才會執行且一次只可以得到一個值想要得到多個值必須執行多次)。
惰性運算的優點
1.避免不必要的計算,帶來性能的提升。 
2.節省空間,使得無線循環的數據結構成為可能

生成器特點

調用函數之后  函數不會執行  會返回一個生成器

每次調用__next__()方法會取到一個值 直到取完最后一個再執行__next__()方法會報錯 StopIteration

初識生成器

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(1)   #sleep一秒看清執行過程
print(next(g1))
初識生成器函數

更多示例

假如我想讓工廠給學生做校服,生產2000000件衣服,我和工廠一說,工廠應該是先答應下來,然后再去生產,我可以一件一件的要,也可以根據學生一批一批的找工廠拿。
而不能是一說要生產2000000件衣服,工廠就先去做生產2000000件衣服,等回來做好了,學生都畢業了。。。

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件衣服。
#剩下的還有很多衣服,我們可以一直拿,也可以放着等想拿的時候再拿
View Code

生成器監聽文件輸入

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)
View Code

生成器取值的方法

1.for循環取值:生成器函數調用的時候返回一個生成器   for循環生成器中的每個元素執行出結果

def produce():
    for i in range(100):
        yield '生產了第%s件衣服'%i

res = produce()
num = 0
for i in res:
    print(i)
    num += 1
    if num == 10:
        break
結果:
生產了第0件衣服
生產了第1件衣服
生產了第2件衣服
生產了第3件衣服
生產了第4件衣服
生產了第5件衣服
生產了第6件衣服
生產了第7件衣服
生產了第8件衣服
生產了第9件衣服
View Code

2.__next__()方法:生成器函數調用的時候返回一個生成器,生成器.__next__()一次只會取到一個值直到取完最后一個再執行__next__()方法會報錯 StopIteration

def produce():
    for i in range(4):
        yield '生產了第%s件衣服'%i

res = produce()

print(res.__next__())
print(res.__next__())
print(res.__next__())

結果:
生產了第0件衣服
生產了第1件衣服
生產了第2件衣服

如果取完最后一個值再執行__next__()方法就會報錯
def produce():
    for i in range(3):
        yield '生產了第%s件衣服'%i

res = produce()

print(res.__next__())
print(res.__next__())
print(res.__next__())
print(res.__next__())

執行結果:
Traceback (most recent call last):
生產了第1件衣服
  File "D:/python/練習/迭代器和生成器.py", line 17, in <module>
生產了第2件衣服
    print(res.__next__())
StopIteration   range范圍只有3個但是執行了4次__next__()方法
View Code

3.數據類型的強制轉換

4.send方法

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

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

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

生成器小結

1.是可迭代對象,返回的是一個生成器對象
2.實現了延遲計算,省內存啊
3.生成器本質和其他的數據類型一樣,都是實現了迭代器協議,只不過生成器附加了一個延遲計算省內存的好處,其余的可迭代對象可沒有這點好處,記住嘍!!!

生成器函數(yield功能)

1.相當於把__iter__和__next__方法封裝到函數內部

2. return 比, return 只能返回一次,而 yield 能返回多次
 
3. 函數暫停已經繼續運行的狀態是通過 yield 保存的
# 用生成器函數
# yield 相當於return控制的是函數的返回值
# x=yield的另外一個特性,接收send傳過來的值,賦值給x
def test():
    print("開始啦")
    first = yield # return 1   first = None
    print("第一次",first)
    yield 2
    print("第二次")
t = test()
print(test().__next__())
res = t.__next__() # next(t)
print(res)
res = t.send("函數停留在first那個位置,我就是給first賦值的")
print(res)

輸出結果
開始啦
None
開始啦
None
第一次 函數停留在first那個位置,也就是給first賦值的2
1 #yield表達形式:
2     food=yield
3 
4 #生成器.send與next(生成器)區別:
5 1.如果函數內yeild是表達式形式,那么必須先next(e)
6 2.二者的共同之處都可以讓函數在上次暫時的位置繼續運行不同之處在於send在出發下一次的執行時,會順便給yield傳一個值
View Code

二.生成器表達式

print(sum(i for i in range(10000)))       
# 表達式一般用for循環 (i for i in range(10000)) # 作用 節省內存,在內部已經實現了__iter__的方法
#三元表達式
name='alex'
name='linhaifeng'
res='SB' if name == 'alex' else 'shuai'
print(res)


#列表解析
li = [i for i in range(10) ]
li = [i for i in range(10) if i > 5]

各種推導式

列表推導式

#[每一個元素或者是和元素相關的操作 for 元素 in 可迭代數據類型]    #遍歷之后挨個處理
res = [ i  for  i  in  range(10)]
#[滿足條件的元素相關的操作 for 元素 in 可迭代數據類型 if 元素相關的條件]   #篩選功能
ret = [i for i in range(30) if i%3 == 0]
# 例二:找到嵌套列表中名字含有兩個‘e’的所有名字 # names
= [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'], # ['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry', 'Eva']] # ret = [name for lst in names for name in lst if name.count('e') ==2] # print(ret)

字典推導式

# 例一:將一個字典的key和value對調
# mcase = {'a': 10, 'b': 34}
# #{10:'a' , 34:'b'}
# mcase_frequency = {mcase[k]: k for k in mcase}
# print(mcase_frequency)

# 例二:合並大小寫對應的value值,將k統一成小寫
# mcase = {'a': 10, 'b': 34, 'A': 7, 'Z': 3}
# #{'a':10+7,'b':34,'z':3}
# mcase_frequency = {k.lower(): mcase.get(k.lower(), 0) + mcase.get(k.upper(), 0) for k in mcase}
# print(mcase_frequency)

集合推導式 

#集合推導式,自帶結果去重功能
# squared = {x**2 for x in [1, -1, 2]}
# print(squared)

應用示例

迭代器應用

文件名:a.txt,文件內容如下:

    apple 10 3

    tesla 100000 1

    mac 3000 2

    lenovo 30000 3

    chicken 10 3

  實現功能:cat a.txt |grep apple

    要求1:定義迭代器函數cat

    要求2:定義迭代器函數grep

    要求3:模擬管道的功能,將cat的處理結果作為grep的輸入
功能要求
import time
#定義迭代器函數cat
def cat(file):  
    with open(file) as f:  #打開a.txt文檔
        f.seek(0)          #光標移動到文檔首行行頭
        while True:
            line=f.readline()   #按行讀取文檔
            if not line:    #采用bool狀態,表示讀取的這行沒有內容
                time.sleep(0.2) 
                # print("--------->")
                continue    #結束本次循環,重新開始循環繼續讀取文檔
            else:
                yield line   #文檔讀取的行有內容,則返回改行
                 
#定義迭代器函數grep
def grep(args,lines):
    for line in lines:  #相當於多次next(r1)
        if args in line:  #args被賦值“apple”,判斷“apple”在不在line這一行內
            yield line    #“apple”在line這一行內,返回該行
 
r1=cat("a.txt")  
r2=grep("apple",r1)
 
for i in r2:   #相當於多次print(next(r2))
    print(i)
實現代碼

生成器應用

把下述函數改成生成器的形式,執行生成器函數的到一個生成器g,然后每次g.send(url),打印頁面的內容,利用g可以無限send

  def get(url):

    def index():

      return urlopen(url).read()

    return index
示例要求
from urllib.request import urlopen
def get():
    print("開始爬取網頁")
    while True:
        url=yield  #將傳入參數賦值給url,可迭代的函數中,yield賦值有表達式就是生成器
        print(urlopen("%s"%url).read())   #輸出爬取url的內容
        print("%s 爬取成功"%url)
 
g=get()      #生成迭代器
g.__next__()    #觸發函數的運行,停留在yield表達式這一行
g.send("http://www.baidu.com")
g.send("http://www.python.org")
實現代碼

 


免責聲明!

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



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