
上篇文章我們介紹了Vscode搭建Python的開發(fā)環(huán)境,幫助了一些小伙伴解決了沒有Pycharm專業(yè)版的窘境,目前市面上出了一本《Visual Studio Code 權(quán)威指南》有興趣的小伙伴可以閱讀下。
大家還記得上篇文末留下的問題么?看到這里大家應(yīng)該就能想到那個問題是使用迭代器和生成器的相關(guān)知識點來解決問題啦。
一、迭代器
百度百科中對迭代器的一個解釋為:迭代器是一種對象,它能夠用來遍歷標(biāo)準(zhǔn)模板庫容器中的部分或全部元素,每一個迭代器對象代表容器中的確定的地址。
注意在Python中所有的集合都是可以迭代的,也就是說可以使用遍歷的方式去獲取其中的元素,那么在Python的中迭代器的支持哪些實現(xiàn)呢,比如:
(1)for循環(huán)
(2)按行遍歷文本文件
(3)列表、字典和集合推導(dǎo)式
(4)元組拆包
比如我們常用的for循環(huán)和文本讀取的操作:
for each in ['Python','學(xué)習(xí)']:
print(each)
和
testfile = '1.txt'
with open(testfile) as f:
for readline in f:
print(readline)
可以看出Python的循環(huán)要比C語言的循環(huán)來的更加的抽象。
那么為什么迭代器支持這些實現(xiàn)呢?拿文本文件的遍歷來說,是因為文本對象支持(或者說實現(xiàn)了)迭代器的協(xié)議。
這里簡單的說明兩個協(xié)議:
迭代器協(xié)議: 對象內(nèi)部實現(xiàn)了next和iter方法,其就是迭代器。
可迭代協(xié)議: 對象內(nèi)部實現(xiàn)了iter方法,其就是可迭代對象。
1.1 可迭代對象
有些小伙伴在進行學(xué)習(xí)的時候,經(jīng)常會看到一個叫iterable的詞,看到這個的時候就說明我們遇到了“可迭代對象”,那么什么是可迭代對象,它有什么特點呢?
在有一些教材上將可直接使用for循環(huán)進行操作的對象為可迭代對象,其實這樣理解也沒有什么太大的問題。如果從可迭代對象的內(nèi)部的實現(xiàn)角度出發(fā),如果一個對象實現(xiàn)了iter方法,我們就可以認(rèn)為這個對象是可迭代的,這也正好與可迭代協(xié)議一致。
比如在list類中就實現(xiàn)了iter函數(shù):
?

補充一點:如果一個對象內(nèi)部實現(xiàn)了getitem方法,而且參數(shù)索引從0開始,那么這種對象也是可迭代的。
在實際的編碼開發(fā)過程中,有什么方法可以直接判斷一個對象是不是可迭代的對象呢?有的:就是使用isinstance()方法來進行判斷,比如:
from collections import Iterable
obj_1 = [1,2,3,4]
print(isinstance(obj_1,Iterable))
輸出True,這說明對象obj_1是可迭代的,實時上列表確實是可以迭代的。
在之前的推文中提到過,在Python中一切皆可為對象,接下來我們看一個示例:
from collections import Iterable
class Iterable_test:
print('我愛python學(xué)堂')
# 定義__iter__方法
def __iter__(self):
pass
f = Iterable_test()
print(isinstance(f,Iterable))
我們定義了一個類,并在類中實現(xiàn)了iter方法,接著我們實例化對象,最后我們判斷這個對象是否可迭代。結(jié)果為True,這正好驗證了上述的點:如果一個對象實現(xiàn)了iter方法,我們就可以認(rèn)為這個對象是可迭代的。
實際上我們還有一個另外一個方法可以進行判斷:使用iter()函數(shù):
num = 100
print(iter(num))
# TypeError: 'int' object is not iterable
輸出報錯這說明我們想判斷的對象不是可迭代對象,一般出現(xiàn)這樣的異常我們try/except處理一下就可以了。再例如:
obj_1 = [1,2,3,4]
print(iter(obj_1))
# <list_iterator object at 0x000001EC8EBC3C18>
這樣類似的輸出就說明我們想要判斷的對象是可迭代對象,使用iter()函數(shù)也是非常方便的。
1.2 迭代器
之前提到,迭代器是一種可以記住遍歷位置的對象,其內(nèi)部實現(xiàn)了iter方法和next方法。既然這樣,迭代器當(dāng)然也屬于可迭代對象了,只不過在Python中是通過可迭代對象來獲取迭代器的。
比如我們在使用for循環(huán)來遍歷string_1 = 'Python'字符串的時候,對象string_1的背后就是存在迭代器的,只是隱式的存在。
下面我們來介紹一下上面涉及到的兩個方法:next方法和iter方法,這也是標(biāo)準(zhǔn)的迭代器接口存在的兩個方法。
1.3 迭代器iter()方法
從迭代器和可迭代對象的角度來看,iter方法是迭代器與可迭代對象都要實現(xiàn)的方法,該方法返回的是當(dāng)前對象迭代器類的實例。直接返回self(即自己本身),以便在應(yīng)該使用可迭代對象的地方使用迭代器, 例如在 for 循環(huán)中。
1.4 迭代器next()方法
返回迭代過程中的每一步,如下一個可用的元素, 如果沒有元素了, 拋出 StopIteration異常。
這里拋出一個問題:python的迭代器為什么一定要實現(xiàn)iter方法?
簡單的解釋:如果迭代器不實現(xiàn)iter方法的話,那么其就不能迭代了,也就不是可迭代的對象。從另外一個角度上來看,在迭代器返回元素的過程中是先調(diào)用iter來獲取迭代對象,然后再調(diào)用可迭代對象的next來實現(xiàn)的。
最后我們來看看實際中我們怎么使用迭代器:
class Fib(object):
def __init__(self):
self.num_1, self.num_2 = 0, 1
def __iter__(self):
return self
def __next__(self):
self.num_1, self.num_2 = self.num_2, self.num_1 + self.num_2
if self.num_1 > 100:
raise StopIteration()
return self.num_1
for each in Fib():
print (each)
稍微解釋一下,每一次for循環(huán)的時候會調(diào)用Fib()中iter的方法,這個方法返回一個可迭代對象,然后調(diào)用該迭代對象的next()方法拿到返回值,直到遇到StopIteration錯誤時退出循環(huán),以上就是使用迭代器實現(xiàn)的Fib()函數(shù)。
實際上,我們可以使用迭代器來實現(xiàn)Fib()函數(shù)的,如下:
def Fib(n):
num_1, num_2 = 0, 1
while num_1 < 100:
print(num_1)
num_1, num_2 = num_2, num_1 + num_2
Fib(2)
當(dāng)然了,如果我們想將next()得到的值反向輸出,我們就可以使用reversed()創(chuàng)建
反序訪問的迭代器。
我們分步驟看看next()會的實際效果:
S = 'python'
S_1 = iter(target)
print(next(S_1)) #p
print(next(S_1)) #y
print(next(S_1)) #t
print(next(S_1)) #h
print(next(S_1)) #o
print(next(S_1)) #h
print(next(S_1)) # Traceback (most recent call last)...StopIteration
二、生成器
百度百科中對生成器(generator)的一個解釋為:生成器是一次生成一個值的特殊類型的函數(shù),可以將其視為可恢復(fù)函數(shù),調(diào)用該函數(shù)將返回一個可用于生成連續(xù)目標(biāo)值的生成器,在函數(shù)的執(zhí)行過程中,yield語句會把你需要的值返回給調(diào)用生成器的地方,然后退出函數(shù),下次調(diào)用生成器函數(shù)的時候又從上次中斷的地方開始執(zhí)行,而生成器內(nèi)的所有變量參數(shù)都會被保存下來供下次調(diào)用的時候使用。
2.1 關(guān)鍵詞yield
作用: yield 的作用就是把一個函數(shù)變成一個生成器,實現(xiàn)了yield 的函數(shù)不再是一個普通函數(shù),Python的解釋器會將其視為一個生成器?;蛘哒fyield是一個具有return功能的關(guān)鍵字,只是含有yield的函數(shù)返回的是個生成器。
一般情況下,包含yield 的函數(shù)我們成為生成器函數(shù),一般包含return 的函數(shù)我們就認(rèn)為是一般函數(shù)。
我們來看一個例子:
def myGenerator() :
mylist = range(1,3)
for i in mylist :
yield i*i
test_generator = myGenerator()
print('test_generator:',test_generator)
for i in test_generator:
print(i)
上述輸出:
test_generator: <generator object myGenerator at 0x000001FACC866DB0>
1
4
可以看出,對象test_generator是一個generator。唯一要注意的是:當(dāng)我們的代碼運行到test_generator = myGenerator()的時候,其實函數(shù)內(nèi)部由于存在yield代碼并不會馬上就執(zhí)行 ,這個函數(shù)只是返回一個生成器對象。然后我們使用for循環(huán)來讀取生成器對象中的值。
那么使用生成器有什么好處呢?我覺得有兩個好處:
精簡代碼量
提高代碼性能
2.2 精簡代碼量
比如我們之前在上述使用迭代器實現(xiàn)的Fib的前N數(shù),我們使用的代碼量還是很多的,如果我們使用yield的話就非常舒服了:
def Fib(n):
num_1, num_2 = 0, 1
while num_1 < n:
yield num_1
num_1, num_2 = num_2, num_1 + num_2
for n in Fib(4):
print(n)
注意的是我們?nèi)匀皇褂脃ield構(gòu)造出了一個生成器函數(shù),通過for循環(huán)來進行數(shù)據(jù)的提取。
2.3 提高代碼性能
第二個優(yōu)點就是提高性能,這也是解決我們上次推文中提及到的問題的關(guān)鍵。我們試想一下在N(假設(shè)N>50)個數(shù)據(jù)中,這樣取數(shù):任意選2個、任意選3個
…任意選N個,把所有這樣選擇的數(shù)都加起來,我們知道這個數(shù)量是非常大的,所有把所有的組合存入列表丟到內(nèi)存中,這很有可能將內(nèi)存撐爆的,因此我們有必要來優(yōu)化性能。
那么使用yield的生成器實際上是怎么優(yōu)化性能的呢?我們來看看怎么實現(xiàn)的:
import itertools
def onegenerate(string_index):
#for number_1 in range(2, len(string_index) + 1):
for number_1 in range(len(string_index), 1, -1):
iter = itertools.combinations(string_index, number_1)
iter_list = list(iter)
for number_2 in range(len(iter_list)):
each = iter_list[number_2]
each_list = list(each)
yield each_list
list_test = [i for i in range(1,21)]
mygene = onegenerate(list_test)
print(mygene)
total = 0
for n in mygene: #迭代器里面的內(nèi)容
# print(n)
total = total + 1
print(total)
大家可以試著運行一下輸出是什么,這里就不告訴大家輸出是什么了。
輸出的結(jié)果是反向的,我們可以使用reversed()進行反轉(zhuǎn)的。
寫到這里,還有一個實際項目中用到的技巧,假設(shè)我們電腦內(nèi)存是8G,我們遇到了16G大小的數(shù)據(jù),這怎么辦呢?沒錯,我們可以使用分塊讀取的技巧。也就是使用答案是使用 yield 構(gòu)造 generator,可以這樣:
def chunk_readtest(file, CHUNK_SIZE):
while True:
chunk_data = file.read(CHUNK_SIZE)
if chunk:
yield chunk_data
else:
return
f = open('yourdata')
# chunk_size為每次讀入數(shù)據(jù)的塊大小
CHUNK_SIZE = 2048
for chunk in chunk_readtest(f,CHUNK_SIZE):
# 使用數(shù)據(jù)的函數(shù)
use_chunk(chunk)
這個技術(shù)在我們處理大數(shù)據(jù)的時候非常有用的哦。
三、總結(jié)
以上就是迭代器和生成器的全部內(nèi)容,其實它們也不是很難,個人認(rèn)為在實際的項目中生成器用的還是多一點,大家可以將重點放在生成器的使用上。對于迭代器,大家也要掌握好,因為它能讓我們明白為什么可以使用for循環(huán)來進行迭代取值。