您現在的位置是:首頁 > 遊戲

Python 迭代器與生成器

由 Hewmbj 發表于 遊戲2022-03-23
簡介PS:關於range是不是一個迭代器print(‘__next__’ in dir(range(12)))#檢視‘__next__’是不是在range()方法執行之後內部是否有__next__print(‘__iter__’ in di

什麼是生成器

迭代

可以被for迴圈的就是可迭代的,目前有字串,列表,元組,字典,集合

from collections 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迴圈取值的現象,再從字面上理解一下,其實迭代就是剛剛說的,可以將某個資料集內的資料“一個挨著一個的取出來”,就

叫做迭代

可迭代協議

現在是從結果分析原因,能被for迴圈的就是“可迭代的”,但是如果正著想,for怎麼知道誰是可迭代的呢?

假如自己寫了一個數據型別,希望這個資料型別裡的東西也可以使用for被一個一個的取出來,那我們就必須滿足for的要求。這個要求就叫做“

協議

”。

可以被迭代要滿足的要求就叫做可迭代協議。可迭代協議的定義非常簡單,就是內部實現了__iter__方法。

print(dir([1,2]))

print(dir((2,3)))

print(dir({1:2}))

print(dir({1,2}))

[‘__add__’, ‘__class__’, ‘__contains__’, ‘__delattr__’, ‘__delitem__’,

‘__dir__’, ‘__doc__’, ‘__eq__’, ‘__format__’, ‘__ge__’, ‘__getattribute__’,

‘__getitem__’, ‘__gt__’, ‘__hash__’, ‘__iadd__’, ‘__imul__’, ‘__init__’,

‘__init_subclass__’, ‘__iter__’, ‘__le__’, ‘__len__’, ‘__lt__’, ‘__mul__’,

‘__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__reversed__’,

‘__rmul__’, ‘__setattr__’, ‘__setitem__’, ‘__sizeof__’, ‘__str__’,

‘__subclasshook__’, ‘append’, ‘clear’, ‘copy’, ‘count’, ‘extend’,

‘index’, ‘insert’, ‘pop’, ‘remove’, ‘reverse’, ‘sort’]

[‘__add__’, ‘__class__’, ‘__contains__’, ‘__delattr__’, ‘__dir__’, ‘__doc__’,

‘__eq__’, ‘__format__’, ‘__ge__’, ‘__getattribute__’, ‘__getitem__’,

‘__getnewargs__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__init_subclass__’,

‘__iter__’, ‘__le__’, ‘__len__’, ‘__lt__’, ‘__mul__’, ‘__ne__’, ‘__new__’,

‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__rmul__’, ‘__setattr__’,

‘__sizeof__’, ‘__str__’, ‘__subclasshook__’, ‘count’, ‘index’]

[‘__class__’, ‘__contains__’, ‘__delattr__’, ‘__delitem__’, ‘__dir__’,

‘__doc__’, ‘__eq__’, ‘__format__’, ‘__ge__’, ‘__getattribute__’, ‘__getitem__’,

‘__gt__’, ‘__hash__’, ‘__init__’, ‘__init_subclass__’, ‘__iter__’, ‘__le__’,

‘__len__’, ‘__lt__’, ‘__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’,

‘__repr__’, ‘__setattr__’, ‘__setitem__’, ‘__sizeof__’, ‘__str__’,

‘__subclasshook__’, ‘clear’, ‘copy’, ‘fromkeys’, ‘get’, ‘items’, ‘keys’,

‘pop’, ‘popitem’, ‘setdefault’, ‘update’, ‘values’]

[‘__and__’, ‘__class__’, ‘__contains__’, ‘__delattr__’, ‘__dir__’, ‘__doc__’,

‘__eq__’, ‘__format__’, ‘__ge__’, ‘__getattribute__’, ‘__gt__’, ‘__hash__’,

‘__iand__’, ‘__init__’, ‘__init_subclass__’, ‘__ior__’, ‘__isub__’, ‘__iter__’,

‘__ixor__’, ‘__le__’, ‘__len__’, ‘__lt__’, ‘__ne__’, ‘__new__’, ‘__or__’, ‘__rand__’,

‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__ror__’, ‘__rsub__’, ‘__rxor__’,

‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__sub__’, ‘__subclasshook__’, ‘__xor__’,

‘add’, ‘clear’, ‘copy’, ‘difference’, ‘difference_update’, ‘discard’,

‘intersection’, ‘intersection_update’, ‘isdisjoint’, ‘issubset’,

‘issuperset’, ‘pop’, ‘remove’, ‘symmetric_difference’,

‘symmetric_difference_update’, ‘union’, ‘update’]

PS:注意

print(dir(‘’)) # dir()方法是檢視括號內的成分的全部方法

結果

[‘__add__’, ‘__class__’, ‘__contains__’, ‘__delattr__’, ‘__dir__’, ‘__doc__’,

‘__eq__’, ‘__format__’, ‘__ge__’, ‘__getattribute__’, ‘__getitem__’,

‘__getnewargs__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__init_subclass__’,

‘__iter__’, ‘__le__’, ‘__len__’, ‘__lt__’, ‘__mod__’, ‘__mul__’, ‘__ne__’,

‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__rmod__’, ‘__rmul__’,

‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__subclasshook__’, ‘capitalize’,

‘casefold’, ‘center’, ‘count’, ‘encode’, ‘endswith’, ‘expandtabs’, ‘find’,

‘format’, ‘format_map’, ‘index’, ‘isalnum’, ‘isalpha’, ‘isdecimal’, ‘isdigit’,

‘isidentifier’, ‘islower’, ‘isnumeric’, ‘isprintable’, ‘isspace’, ‘istitle’,

‘isupper’, ‘join’, ‘ljust’, ‘lower’, ‘lstrip’, ‘maketrans’, ‘partition’,

‘replace’, ‘rfind’, ‘rindex’, ‘rjust’, ‘rpartition’, ‘rsplit’, ‘rstrip’,

‘split’, ‘splitlines’, ‘startswith’, ‘strip’, ‘swapcase’, ‘title’, ‘translate’,

‘upper’, ‘zfill’]

總結

現在所知道的:可以被for迴圈的都是可迭代的,要想可迭代,內部必須有一個__iter__方法。

__iter__方法作用

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

# 結果

執行了list([1,2])的__iter__方法,我們好像得到了一個list_iterator,現在我們又得到了一個新名詞——iterator。

iterator,是一個計算機中的專屬名詞,叫做迭代器。

迭代器協議

既什麼叫“可迭代”之後,又一個歷史新難題,什麼叫“迭代器”?

雖然不知道什麼叫迭代器,但是現在已經有一個迭代器了,這個迭代器是一個列表的迭代器。

這個列表的迭代器比起列表來說實現了哪些新方法,這樣就能揭開迭代器的神秘面紗了吧?

‘’‘

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__())

使用迭代器next方法來寫一個不依賴for的遍歷

l = [1,2,3,4]

l_iter = l。__iter__()

item = l_iter。__next__()

print(item)

item = l_iter。__next__()

print(item)

item = l_iter。__next__()

print(item)

item = l_iter。__next__()

print(item) # 到此處是正確的,繼續向下執行則因迭代器中沒有元素而丟擲異常StopIteration告訴我們,列表中已經沒有有效的元素了

item = l_iter。__next__()

print(item)

防止丟擲異常的辦法,使用異常處理機制把異常處理掉

l = [1,2,3,4]

l_iter = l。__iter__()

while True:

try:

item = l_iter。__next__()

print(item)

except StopIteration:

break

我們就使用while迴圈實現了原本for迴圈做的事情,我們是從誰那兒獲取一個一個的值呀?是不是就是l_iter?好了,這個l_iter就是一個迭代器。

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

PS:關於range是不是一個迭代器

print(‘__next__’ in dir(range(12))) #檢視‘__next__’是不是在range()方法執行之後內部是否有__next__

print(‘__iter__’ in dir(range(12))) #檢視‘__next__’是不是在range()方法執行之後內部是否有__iter__

from collections import Iterator

print(isinstance(range(100000000),Iterator)) #驗證range執行之後得到的結果不是一個迭代器

生成器

迭代器有兩種:一種是呼叫方法直接返回的,一種是可迭代物件透過執行iter方法得到的,迭代器有的好處是可以節省記憶體。

如果在某些情況下,我們也需要節省記憶體,就只能自己寫。我們自己寫的這個能實現迭代器功能的東西就叫生成器。

Python中提供的生成器

生成器函式:常規函式定義,但是,使用yield語句而不是return語句返回結果。yield語句一次返回一個結果,在每個結果中間,掛起函式的狀態,以便下次重它離開的地方繼續執行

生成器表示式:類似於列表推導,但是,生成器返回按需產生結果的一個物件,而不是一次構建一個結果列表

生成器Generator

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

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

生成器函式

一個包含yield關鍵字的函式就是一個生成器函式。yield可以為我們從函式中返回值,但是yield又不同於return,return的執行意味著程式的結束,呼叫生成器函式不會得到返回的具體的值,而是得到一個可迭代的物件。每一次獲取這個可迭代物件的值,就能推動函式的執行,獲取新的返回值。直到函式執行結束。

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件衣服。

#剩下的還有很多衣服,我們可以一直拿,也可以放著等想拿的時候再拿

生成器監控檔案輸出

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)

計算移動平均值——預激生成的裝飾器

def init(func): #在呼叫被裝飾生成器函式的時候首先用next啟用生成器

def inner(*args,**kwargs):

g = func(*args,**kwargs)

next(g)

return g

return inner

@init

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) 在裝飾器中執行了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()))

def gen2():

yield from ‘AB’

yield from range(3)

print(list(gen2()))

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

#讓我來啟動點東西吧

run_list=[‘run %s’ %i for i in range(10)] #列表解析

#別直接全啟動了,咱們一個一個來

run_you=(‘run %s’ %i for i in range(10))#生成器表示式

print(run_you)

print(next(run_you)) #next本質就是呼叫__next__

print(run_you。__next__())

print(next(run_you))

列表推導式和生成器表示式總結

把列表解析的[]換成()得到的就是生成器表示式

列表解析與生成器表示式都是一種便利的程式設計方式,只不過生成器表示式更節省記憶體

Python不但使用迭代器協議,讓for迴圈變得更加通用。大部分內建函式,也是使用迭代器協議訪問物件的。例如, sum函式是Python的內建函式,該函式使用迭代器協議訪問物件,而生成器實現了迭代器協議,所以,我們可以直接這樣計算一系列值的和

sum(x ** 2 for x in range(4))

而不用多此一舉的先構造一個列表

sum([x ** 2 for x in range(4)])

使用生成器優點

延遲計算,一次返回一個結果。也就是說,它不會一次生成所有的結果,這對於大資料量處理,將會非常有用。

提高程式碼可讀性

#我要學Python#

Python 迭代器與生成器

Python 迭代器與生成器

推薦文章