Python基礎(一)迭代器、生成器
迭代(iteration)
如果給定一個list或tuple,我們可以通過for迴圈來遍歷這個list或tuple,這種遍歷我們稱為迭代(Iteration
)
在Python中,迭代是通過for ... in
來完成的,所以,當我們使用for迴圈時,只要作用於一個可迭代物件,for迴圈就可以正常執行,而我們不太關心該物件究竟是list還是其他資料型別。
因為
dict
的儲存不是按照list的方式順序排列,所以,迭代出的結果順序很可能不一樣。
預設情況下,dict迭代的是key。如果要迭代value,可以用for value in d.values()
,如果要同時迭代key和value,可以用for k, v in d.items()
。
可以通過collections
模組的Iterable
型別判斷一個物件是否為可迭代物件:
>>> from collections import Iterable
>>> isinstance('abc',Iterable)
True
>>> isinstance(123,Iterable) #int型別不可迭代
False
>>> isinstance([1,2,3],Iterable)
True
任何可迭代物件都可以作用於for迴圈,包括我們自定義的資料型別,只要符合迭代條件,就可以使用for迴圈。
三元運算與列表解析
三元運算:在賦值變數的時候,可以直接加判斷,然後賦值
if 1>3:
temp = 'gt'
else:
temp = 'lt'
print(temp)
result = 'gt' if 1>3 else 'lt'
print(result)
列表解析
# user = []
# for i in range(10):
# user.append('user%s' %i)
# print(user)
user = ['user%s' %i for i in range(10)]
print(user) #['user0' , 'user1', 'user2', 'user3', 'user4', 'user5', 'user6', 'user7', 'user8', 'user9']
user1 = ['user%s' %i for i in range(10) if i>5]
print(user1) #['user6', 'user7', 'user8', 'user9']
迭代器(iterator)
可迭代物件(iterable):在python中list、tuple、dict、set、str
等資料型別可以直接作用於for迴圈,這些可以直接作用於for迴圈的物件統稱為可迭代物件(Iterable)
。(實現:物件內部定義一個__iter__
方法)
迭代器(iterator):可以被next()
函式呼叫並不斷返回下一個值的物件稱為迭代器(Iterator)
。
迭代器協議:指物件必須提供一個next
方法,執行該方法要麼返回迭代中的下一項,要麼就引起一個Stoplteration
異常,以終止迭代(只能往後走不能往前退)
協議是一種約定,可迭代物件實現了迭代器協議,python的內部工具(如
for,sum,min,max
函式等)使用迭代器協議訪問物件
可以使用isinstance()
判斷一個物件是否是Iterator
物件:
>>> from collections import Iterator
>>> isinstance('abc',Iterator)
False
>>> isinstance((i for i in range(10)),Iterator) #生成器都是Iterator物件
True
字串、列表、元組、字典、集合、檔案物件這些都不是可迭代物件,只不過在for迴圈時呼叫了他們內部的__iter__
方法,把他們變成了可迭代物件。然後for迴圈呼叫可迭代物件的__next__
方法取值,而for迴圈會捕捉Stoplteration
異常,以終止迭代
>>> isinstance(iter('abc'),Iterator)
True
>>> l = [1,2,3]
>>> iter_l = l.__iter__()
>>> print(iter_l.__next__())
1
>>> print(iter_l.__next__())
2
>>> print(iter_l.__next__())
3
>>> print(iter_l.__next__())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
總結:
- 凡是可作用於
for
迴圈的物件都是Iterable
型別; - 凡是可作用於
next()
函式的物件都是Iterator
型別,它們表示一個惰性計算的序列; - 集合資料型別如
list、dict、str
等是Iterable
但不是Iterator
,不過可以通過iter()
函式獲得一個Iterator
物件。 - Python的
for
迴圈本質上就是通過不斷呼叫next()
函式實現的,例如:
l = [1,2,3]
# for i in l:
# print(i) #for迴圈完全等價於如下內容
iter_l = l.__iter__()
while True:
try:
print(iter_l.__next__())
except StopIteration:
print('迭代完畢,迴圈終止')
break
生成器(generator)
通過列表解析,我們可以直接建立一個列表。但是,受到記憶體限制,列表容量肯定是有限的。而且,建立一個包含100萬個元素的列表,不僅佔用很大的儲存空間,如果我們僅僅需要訪問前面幾個元素,那後面絕大多數元素佔用的空間都白白浪費了。
所以,如果列表元素可以按照某種演算法推算出來,那我們是否可以在迴圈的過程中不斷推算出後續的元素呢?這樣就不必建立完整的list,從而節省大量的空間。在Python中,這種一邊迴圈一邊計算的機制,稱為生成器(generator)
。
生成器可以理解為一種資料型別,這種資料型別自動實現了迭代器協議(其他的資料型別需要呼叫自己內建的__iter__
方法),所以生成器就是可迭代物件。
生成器分類及在python中的表現形式:(python有兩種不同的方式提供生成器)
- 生成器函式:常規函式定義,但是,使用
yield
語句而不是return
語句返回結果。yield語句一次返回一個結果,在每個結果中間,掛起函式的狀態,以便下次重它離開的地方繼續執行 - 生成器表示式:類似於列表推導,但是,生成器返回按需產生結果的一個物件,而不是一次構建一個結果列表
生成器表示式:
#列表解析
user = ['user%s' %i for i in range(10)]
#生成器表示式
user = ('user%s' %i for i in range(10))
user_scq = ('user%s' %i for i in range(10))
print(user_scq) #<generator object <genexpr> at 0x0000000001DEEF10>
print(next(user_scq)) #user0
print(user_scq.__next__()) #user1
把列表解析的[]
換成()
得到的就是生成器表示式;列表解析和生成器表示式都是一種便利的程式設計方式,只不過生成器表示式更節省記憶體
生成器函式:
def test():
for i in range(10):
yield i
res = test()
print(res) #<generator object test at 0x0000000001DEEF10>
print(next(res)) #0
print(res.__next__()) #1
生成器總結:
- 語法上和函式類似:生成器函式和常規函式幾乎是一樣的。他們都是使用
def
語句進行定義,差別在於生成器使用yield
語句返回一個值,而常規函式使用return
函式返回一個值 - 自動實現迭代協議:對於生成器,python會自動實現迭代器協議,以便應用到迭代背景中(如for迴圈,sum函式)。由於生成器自動實現了迭代協議,所以我們可以呼叫它的
next
方法,並且在沒有值可以返回的時候,生成器自動產生Stoplterration
異常 - 掛起狀態:生成器使用
yield
語句返回一個值。yield
語句掛起該生成器函式的狀態,保留足夠的資訊,以便之後從它離開的地方繼續執行。
優點:
1.生成器的好處是延遲計算,一次返回一個結果。也就是說,它不會一次生成所有的結果,這對於大資料處理將會非常有用
#列表解析
sum([i for i in range(10000000000)]) #記憶體佔用大,機器容易卡死
#生成器表示式
sum(i for i in range(10000000000)) #幾乎不佔記憶體
2.生成器還能有效提高程式碼可讀性
注意事項:生成器只能遍歷一次
練習:讀取檔案,計算出各省人口占總人口的百分比
#111檔案
{'name':'北京','population':10000}
{'name':'河北','population':30000}
{'name':'山東','population':15000}
{'name':'山西','population':24000}
def get_population():
with open('111','r',encoding='utf-8') as f:
for i in f:
yield i
res = get_population()
all_population = sum(eval(i)['population'] for i in res)
print(all_population)
for i in get_population():
i_dic = eval(i)
print(i_dic['name'],'%s %%'%(i_dic['population'] / all_population * 100))