1. 程式人生 > >Python基礎(一)迭代器、生成器

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

總結:

  1. 凡是可作用於for迴圈的物件都是Iterable型別;
  2. 凡是可作用於next()函式的物件都是Iterator型別,它們表示一個惰性計算的序列;
  3. 集合資料型別如list、dict、str等是Iterable但不是Iterator,不過可以通過iter()函式獲得一個Iterator物件。
  4. 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有兩種不同的方式提供生成器)

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

生成器表示式:

#列表解析
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

生成器總結:

  1. 語法上和函式類似:生成器函式和常規函式幾乎是一樣的。他們都是使用def語句進行定義,差別在於生成器使用yield語句返回一個值,而常規函式使用return函式返回一個值
  2. 自動實現迭代協議:對於生成器,python會自動實現迭代器協議,以便應用到迭代背景中(如for迴圈,sum函式)。由於生成器自動實現了迭代協議,所以我們可以呼叫它的next方法,並且在沒有值可以返回的時候,生成器自動產生Stoplterration異常
  3. 掛起狀態:生成器使用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))