這篇博客寫了好久,其實寫每一篇博客用的時間仍是挺長的,不夠這有利於本身的學習,也想分享一下。以前也說了建立了一個微信羣,Python 學習討論羣,如今只有 40 個左右的小夥伴,若是有興趣加入學習討論的話,能夠加我微信:androidwed
,拉你進羣。想看回以前的文章,也能夠經過 Gitbook 查看,歡迎提出問題和點下 star,及時查看更新。html
什麼叫作迭代?python
好比在 Java 中,咱們經過 List 集合的下標來遍歷 List 集合中的元素,在 Python 中,給定一個 list 或 tuple,咱們能夠經過 for 循環來遍歷這個 list 或 tuple ,這種遍歷就是迭代。android
但是,Python 的 for
循環抽象程度要高於 Java 的 for
循環的,爲何這麼說呢?由於 Python 的 for
循環不只能夠用在 list 或tuple 上,還能夠做用在其餘可迭代對象上。也就是說,只要是可迭代的對象,不管有沒有下標,都是能夠迭代的。git
好比:算法
# -*- coding: UTF-8 -*-
# 一、for 循環迭代字符串
for char in 'liangdianshui' :
print ( char , end = ' ' )
print('\n')
# 二、for 循環迭代 list
list1 = [1,2,3,4,5]
for num1 in list1 :
print ( num1 , end = ' ' )
print('\n')
# 三、for 循環也能夠迭代 dict (字典)
dict1 = {'name':'兩點水','age':'23','sex':'男'}
for key in dict1 : # 迭代 dict 中的 key
print ( key , end = ' ' )
print('\n')
for value in dict1.values() : # 迭代 dict 中的 value
print ( value , end = ' ' )
print ('\n')
# 若是 list 裏面一個元素有兩個變量,也是很容易迭代的
for x , y in [ (1,'a') , (2,'b') , (3,'c') ] :
print ( x , y )複製代碼
輸出的結果以下:編程
l i a n g d i a n s h u i
1 2 3 4 5
name age sex
兩點水 23 男
1 a
2 b
3 c複製代碼
上面簡單的介紹了一下迭代,迭代是 Python 最強大的功能之一,是訪問集合元素的一種方式。如今正式進入主題:迭代器,迭代器是一個能夠記住遍歷的位置的對象。微信
迭代器對象從集合的第一個元素開始訪問,直到全部的元素被訪問完結束。app
迭代器只能往前不會後退。函數
迭代器有兩個基本的方法:iter() 和 next(),且字符串,列表或元組對象均可用於建立迭代器,迭代器對象可使用常規 for 語句進行遍歷,也可使用 next() 函數來遍歷。學習
具體的實例:
# 一、字符創建立迭代器對象
str1 = 'liangdianshui'
iter1 = iter ( str1 )
# 二、list對象建立迭代器
list1 = [1,2,3,4]
iter2 = iter ( list1 )
# 三、tuple(元祖) 對象建立迭代器
tuple1 = ( 1,2,3,4 )
iter3 = iter ( tuple1 )
# for 循環遍歷迭代器對象
for x in iter1 :
print ( x , end = ' ' )
print('\n------------------------')
# next() 函數遍歷迭代器
while True :
try :
print ( next ( iter3 ) )
except StopIteration :
break複製代碼
最後輸出的結果:
l i a n g d i a n s h u i
------------------------
1
2
3
4複製代碼
以前通過咱們的學習,都知道如何建立一個 list ,但是有些狀況,用賦值的形式建立一個 list 太麻煩了,特別是有規律的 list ,一個一個的寫,一個一個賦值,太麻煩了。好比要生成一個有 30 個元素的 list ,裏面的元素爲 1 - 30 。咱們能夠這樣寫:
# -*- coding: UTF-8 -*-
list1=list ( range (1,31) )
print(list1)複製代碼
輸出的結果:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]複製代碼
這個其實在以前也有提到過:好比有個例子,打印九九乘法表,用這個方法其實就幾句代碼就能夠了,具體能夠看以前的這個章節:條件語句和循環語句綜合實例
可是,若是用到 list 生成式,能夠一句代碼就生成九九乘法表了。具體看代碼:
print('\n'.join([' '.join ('%dx%d=%2d' % (x,y,x*y) for x in range(1,y+1)) for y in range(1,10)]))複製代碼
最後輸出的結果:
1x1= 1
1x2= 2 2x2= 4
1x3= 3 2x3= 6 3x3= 9
1x4= 4 2x4= 8 3x4=12 4x4=16
1x5= 5 2x5=10 3x5=15 4x5=20 5x5=25
1x6= 6 2x6=12 3x6=18 4x6=24 5x6=30 6x6=36
1x7= 7 2x7=14 3x7=21 4x7=28 5x7=35 6x7=42 7x7=49
1x8= 8 2x8=16 3x8=24 4x8=32 5x8=40 6x8=48 7x8=56 8x8=64
1x9= 9 2x9=18 3x9=27 4x9=36 5x9=45 6x9=54 7x9=63 8x9=72 9x9=81複製代碼
不過,這裏咱們先要了解如何建立 list 生成式
首先,lsit 生成式的語法爲:
[expr for iter_var in iterable]
[expr for iter_var in iterable if cond_expr]複製代碼
第一種語法:首先迭代 iterable 裏全部內容,每一次迭代,都把 iterable 裏相應內容放到iter_var 中,再在表達式中應用該 iter_var 的內容,最後用表達式的計算值生成一個列表。
第二種語法:加入了判斷語句,只有知足條件的內容才把 iterable 裏相應內容放到 iter_var 中,再在表達式中應用該 iter_var 的內容,最後用表達式的計算值生成一個列表。
其實不難理解的,由於是 list 生成式,所以確定是用 [] 括起來的,而後裏面的語句是把要生成的元素放在前面,後面加 for 循環語句或者 for 循環語句和判斷語句。
例子:
# -*- coding: UTF-8 -*-
lsit1=[x * x for x in range(1, 11)]
print(lsit1)複製代碼
輸出的結果:
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]複製代碼
能夠看到,就是把要生成的元素 x * x 放到前面,後面跟 for 循環,就能夠把 list 建立出來。那麼 for 循環後面有 if 的形式呢?又該如何理解:
# -*- coding: UTF-8 -*-
lsit1= [x * x for x in range(1, 11) if x % 2 == 0]
print(lsit1)複製代碼
輸出的結果:
[4, 16, 36, 64, 100]複製代碼
這個例子是爲了求 1 到 10 中偶數的平方根,上面也說到, x * x
是要生成的元素,後面那部分其實就是在 for 循環中嵌套了一個 if 判斷語句。
那麼有了這個知識點,咱們也能夠猜測出,for 循環裏面也嵌套 for 循環。具體示例:
# -*- coding: UTF-8 -*-
lsit1= [(x+1,y+1) for x in range(3) for y in range(5)]
print(lsit1)複製代碼
輸出的結果:
[(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (3, 1), (3, 2), (3, 3), (3, 4), (3, 5)]複製代碼
其實知道了 list 生成式是怎樣組合的,就不難理解這個東西了。由於 list 生成式只是把以前學習的知識點進行了組合,換成了一種更簡潔的寫法而已。
經過上面的學習,能夠知道列表生成式,咱們能夠直接建立一個列表。可是,受到內存限制,列表容量確定是有限的。並且,建立一個包含 1000 萬個元素的列表,不只佔用很大的存儲空間,若是咱們僅僅須要訪問前面幾個元素,那後面絕大多數元素佔用的空間都白白浪費了。
因此,若是列表元素能夠按照某種算法推算出來,那咱們是否能夠在循環的過程當中不斷推算出後續的元素呢?這樣就沒必要建立完整的 list,從而節省大量的空間。在 Python 中,這種一邊循環一邊計算的機制,稱爲生成器:generator。
在 Python 中,使用了 yield 的函數被稱爲生成器(generator)。
跟普通函數不一樣的是,生成器是一個返回迭代器的函數,只能用於迭代操做,更簡單點理解生成器就是一個迭代器。
在調用生成器運行的過程當中,每次遇到 yield 時函數會暫停並保存當前全部的運行信息,返回yield的值。並在下一次執行 next()方法時從當前位置繼續運行。
那麼如何建立一個生成器呢?
最簡單最簡單的方法就是把一個列表生成式的 []
改爲 ()
# -*- coding: UTF-8 -*-
gen= (x * x for x in range(10))
print(gen)複製代碼
輸出的結果:
generator objectat 0x0000000002734A40 複製代碼
建立 List 和 generator 的區別僅在於最外層的 []
和 ()
。可是生成器並不真正建立數字列表, 而是返回一個生成器,這個生成器在每次計算出一個條目後,把這個條目「產生」 ( yield ) 出來。 生成器表達式使用了「惰性計算」 ( lazy evaluation,也有翻譯爲「延遲求值」,我覺得這種按需調用 call by need 的方式翻譯爲惰性更好一些),只有在檢索時才被賦值( evaluated ),因此在列表比較長的狀況下使用內存上更有效。
那麼居然知道了如何建立一個生成器,那麼怎麼查看裏面的元素呢?
按咱們的思惟,遍歷用 for 循環,對了,咱們能夠試試:
# -*- coding: UTF-8 -*-
gen= (x * x for x in range(10))
for num in gen :
print(num)複製代碼
沒錯,直接這樣就能夠遍歷出來了。固然,上面也提到了迭代器,那麼用 next() 能夠遍歷嗎?固然也是能夠的。
上面也提到,建立生成器最簡單最簡單的方法就是把一個列表生成式的 []
改爲 ()
。爲啥忽然來個以函數的形式來建立呢?
其實生成器也是一種迭代器,可是你只能對其迭代一次。這是由於它們並無把全部的值存在內存中,而是在運行時生成值。你經過遍從來使用它們,要麼用一個「for」循環,要麼將它們傳遞給任意能夠進行迭代的函數和結構。並且實際運用中,大多數的生成器都是經過函數來實現的。那麼咱們該如何經過函數來建立呢?
先不急,來看下這個例子:
# -*- coding: UTF-8 -*-
def my_function():
for i in range(10):
print ( i )
my_function()複製代碼
輸出的結果:
0
1
2
3
4
5
6
7
8
9複製代碼
若是咱們須要把它變成生成器,咱們只須要把 print ( i )
改成 yield i
就能夠了,具體看下修改後的例子:
# -*- coding: UTF-8 -*-
def my_function():
for i in range(10):
yield i
print(my_function())複製代碼
輸出的結果:
generator object my_function at 0x0000000002534A40複製代碼
可是,這個例子很是不適合使用生成器,發揮不出生成器的特色,生成器的最好的應用應該是:你不想同一時間將全部計算出來的大量結果集分配到內存當中,特別是結果集裏還包含循環。由於這樣會耗很大的資源。
好比下面是一個計算斐波那契數列的生成器:
# -*- coding: UTF-8 -*-
def fibon(n):
a = b = 1
for i in range(n):
yield a
a, b = b, a + b
# 引用函數
for x in fibon(1000000):
print(x , end = ' ')複製代碼
運行的效果:
你看,運行一個這麼打的參數,也不會說有卡死的狀態,由於這種方式不會使用太大的資源。這裏,最難理解的就是 generator 和函數的執行流程不同。函數是順序執行,遇到 return 語句或者最後一行函數語句就返回。而變成 generator 的函數,在每次調用 next() 的時候執行,遇到 yield語句返回,再次執行時從上次返回的 yield 語句處繼續執行。
好比這個例子:
# -*- coding: UTF-8 -*-
def odd():
print ( 'step 1' )
yield ( 1 )
print ( 'step 2' )
yield ( 3 )
print ( 'step 3' )
yield ( 5 )
o = odd()
print( next( o ) )
print( next( o ) )
print( next( o ) )複製代碼
輸出的結果:
step 1
1
step 2
3
step 3
5複製代碼
能夠看到,odd 不是普通函數,而是 generator,在執行過程當中,遇到 yield 就中斷,下次又繼續執行。執行 3 次 yield 後,已經沒有 yield 能夠執行了,若是你繼續打印 print( next( o ) )
,就會報錯的。因此一般在 generator 函數中都要對錯誤進行捕獲。
經過學習了生成器,咱們能夠直接利用生成器的知識點來打印楊輝三角:
# -*- coding: UTF-8 -*-
def triangles( n ): # 楊輝三角形
L = [1]
while True:
yield L
L.append(0)
L = [ L [ i -1 ] + L [ i ] for i in range (len(L))]
n= 0
for t in triangles( 10 ): # 直接修改函數名便可運行
print(t)
n = n + 1
if n == 10:
break複製代碼
輸出的結果爲:
[1]
[1, 1]
[1, 2, 1]
[1, 3, 3, 1]
[1, 4, 6, 4, 1]
[1, 5, 10, 10, 5, 1]
[1, 6, 15, 20, 15, 6, 1]
[1, 7, 21, 35, 35, 21, 7, 1]
[1, 8, 28, 56, 70, 56, 28, 8, 1]
[1, 9, 36, 84, 126, 126, 84, 36, 9, 1]複製代碼
由於迭代器和生成器基本是互通的,所以有些知識點須要綜合在一塊兒
反向迭代,應該也是常有的需求了,好比從一開始迭代的例子裏,有個輸出 list 的元素,從 1 到 5 的
list1 = [1,2,3,4,5]
for num1 in list1 :
print ( num1 , end = ' ' )複製代碼
那麼咱們從 5 到 1 呢?這也很簡單, Python 中有內置的函數 reversed()
list1 = [1,2,3,4,5]
for num1 in reversed(list1) :
print ( num1 , end = ' ' )複製代碼
方向迭代很簡單,但是要注意一點就是:反向迭代僅僅當對象的大小可預先肯定或者對象實現了 __reversed__()
的特殊方法時才能生效。 若是二者都不符合,那你必須先將對象轉換爲一個列表才行
其實不少時候咱們能夠經過在自定義類上實現 __reversed__()
方法來實現反向迭代。不過有些知識點在以前的篇節中尚未提到,不過能夠相應的看下,有編程基礎的,學完上面的知識點應該也能理解的。
# -*- coding: UTF-8 -*-
class Countdown:
def __init__(self, start):
self.start = start
def __iter__(self):
# Forward iterator
n = self.start
while n > 0:
yield n
n -= 1
def __reversed__(self):
# Reverse iterator
n = 1
while n <= self.start:
yield n
n += 1
for rr in reversed(Countdown(30)):
print(rr)
for rr in Countdown(30):
print(rr)複製代碼
輸出的結果是 1 到 30 而後 30 到 1 ,分別是順序打印和倒序打印
你想同時迭代多個序列,每次分別從一個序列中取一個元素。你遇到過這樣的需求嗎?
爲了同時迭代多個序列,使用 zip() 函數,具體示例:
# -*- coding: UTF-8 -*-
names = ['laingdianshui', 'twowater', '兩點水']
ages = [18, 19, 20]
for name, age in zip(names, ages):
print(name,age)複製代碼
輸出的結果:
laingdianshui 18
twowater 19
兩點水 20複製代碼
其實 zip(a, b) 會生成一個可返回元組 (x, y) 的迭代器,其中 x 來自 a,y 來自 b。 一旦其中某個序列到底結尾,迭代宣告結束。 所以迭代長度跟參數中最短序列長度一致。注意理解這句話喔,也就是說若是 a , b 的長度不一致的話,以最短的爲標準,遍歷完後就結束。
利用 zip()
函數,咱們還可把一個 key 列表和一個 value 列表生成一個 dict (字典),以下:
# -*- coding: UTF-8 -*-
names = ['laingdianshui', 'twowater', '兩點水']
ages = [18, 19, 20]
dict1= dict(zip(names,ages))
print(dict1)複製代碼
輸出以下結果:
{'laingdianshui': 18, 'twowater': 19, '兩點水': 20}複製代碼
這裏提一下, zip()
是能夠接受多於兩個的序列的參數,不只僅是兩個。