Python切片、迭代、列表生成式、生成器、迭代器
切片:
在Python中對於具有序列結構的資料來說都可以使用切片操作,比如列表(list)中,我們可以用切片取出其中一部分元素。
需要注意的是序列物件某個索引位置返回的是一個元素,而切片操作返回是和被切片物件相同型別物件的副本
如:
>>> alist = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> alist[0]
0
>>> alist[0:1]
[0]
執行截圖如下:
切片的語法表示式:
[start_index : end_index : step]
start_index表示起始索引;
stop_index表示結束索引;
step
表示步長,步長不能為0,且預設值為1;
start_index和stop_index不論是正數還是負數索引還是混用都可以,但是要保證 list[stop_index]元素的位置必須在list[start_index]元素的位置右邊,否則取不出元素。
如:
>>> L[1:-2]
[1,2,3,4,5,6,7]
執行截圖如下:
切片操作是指按照步長,擷取從起始索引到結束索引,但不包含結束索引(也就是結束索引減1)的所有元素。
python3
支援切片操作的資料型別有list
、tuple
、string
、unicode
、range;
切片返回的結果型別與原物件型別一致;
切片不會改變原物件,而是重新生成了一個新的物件。
對元組(tuple)也可以使用切片,切片的結果也是一個元組(tuple)。
如:
>>> (0, 1, 2, 3, 4, 5)[:3]
(0, 1, 2)
對字串也可以用切片,切片時字串看成是一個list,每個元素是一個字元。切片結果仍是字串。
>>> 'ABCDEFG'[:3]
'ABC'
執行截圖如下:
迭代:
在Python中,對一個列表(list)或元組(tuple)通過for in迴圈來遍歷它,這種遍歷就叫做迭代(Iteration)。
Python中列表(list)、元組(tuple)、字典(Dictionary)、字串、生成器(generator)都是可迭代物件。
注意只要是可以迭代的物件,無論有無下標,都可以迭代。
如:
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> for key in d:
... print(key)
...
a
c
b
執行截圖如下:
預設情況下,dict迭代的是key。如果要迭代value,可以用for value in d.values()
,如果要同時迭代key和value,可以用for k, v in d.items()
。
字串的迭代舉例:
>>> for ch in 'ABC':
... print(ch)
...
A
B
C
執行截圖如下:
Python中提供了一個判斷函式來判斷一個物件是否是可迭代物件。
即collections模組裡的Iterable類,使用該類的isinstance()函式即可判斷。
如:
>>> from collections import Iterable
>>> isinstance('abc', Iterable) # str是否可迭代
True
>>> isinstance([1,2,3], Iterable) # list是否可迭代
True
>>> isinstance(123, Iterable) # 整數是否可迭代
False
如果在迭代時我們想同時取得元素的下標怎麼辦?(類似C/C++for迴圈中從陣列取得某個元素後同時得到其下標)
Python內建的enumerate()
函式可以把一個list變成索引-元素對,這樣就可以在for
迴圈中同時迭代索引和元素本身。
如:
>>> for i, value in enumerate(['A', 'B', 'C']):
... print(i, value)
...
0 A
1 B
2 C
執行截圖如下:
列表生成式:
列表生成式即List Comprehensions,是Python內建的用來生成列表(list)的特定語法形式的表示式。
列表生成式的格式:
[exp for iter_var in iterable]
執行過程:
迭代iterable中的每個元素;
每次迭代都先把結果賦值給iter_var,然後通過exp得到一個新的計算值;
最後把所有通過exp得到的計算值以一個新列表的形式返回。
如,要生成一個元素從1到10的列表(list):
>>> list(range(1, 11))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
再比如,生成1到10各自的平方的列表(list):
>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
把要生成的元素x * x
放到前面,後面跟for
迴圈,就可以把創建出這個list。
執行截圖如下:
我們還可以進一步在for迴圈後面加上if判斷,篩選出偶數的平方的元素的列表(list):
>>> [x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]
執行截圖如下:
生成器:
通過上面列表生成式,我們可以直接建立一個列表。但是列表容量肯定是有限的,而且元素很多時,列表會佔用很大的儲存空間,且不是所有元素都需要訪問,這就造成了浪費。
如果列表元素可以按照某種演算法推算出來,在迴圈的過程中不斷推算出後續的元素,這樣就不必建立完整的列表(list),從而節省大量的空間。在Python中生成器(generator)就是這樣一種一邊迴圈一邊計算的機制。
生成器的構造方式:
使用類似列表生成式的方式生成 (2*n + 1 for n in range(3, 11)),即把列表生成式最外層方括號改成圓括號;
使用包含yield的函式來生成。
如:
>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>
注意:
Python 3.3之前的版本中,不允許迭代函式法中包含return語句。
注意此時我們無法打印出g代表的generator的每一個元素。
此時我們可以通過next()
函式獲得generator的下一個返回值。
>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> next(g)
16
>>> next(g)
25
>>> next(g)
36
>>> next(g)
49
>>> next(g)
64
>>> next(g)
81
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
當計算到最後一個元素,沒有更多的元素時,Python丟擲StopIteration
的錯誤。
在實際應用時,我們往往使用for
迴圈,因為generator也是可迭代物件。
如:
>>> g = (x * x for x in range(10))
>>> for n in g:
... print(n)
...
0
1
4
9
16
25
36
49
64
81
執行截圖如下:
當推算的演算法比較複雜(即上例的x*x),用類似列表生成式的for
迴圈無法實現的時候,我們還可以用函式來實現。
如著名的斐波拉契數列(Fibonacci):
除第一個和第二個數外,任意一個數都可由前兩個數相加得到。
1, 1, 2, 3, 5, 8, 13, 21, 34, ...
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b
n = n + 1
return 'done'
該函式輸出如下:
>>> fib(6)
1
1
2
3
5
8
'done'
上面的函式只要稍加改造就可以變成generator,即只把print(b)
改為yield b
:
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'
如果一個函式定義中包含yield
關鍵字,那麼這個函式就不再是一個普通函式,而是一個generator。generator在執行過程中,遇到yield關鍵字就會中斷執行,下次呼叫則繼續從上次中斷的位置(即yield語句的下一條語句)繼續執行。
把函式改成generator後,我們基本上從來不會用next()
來獲取下一個返回值,而是直接使用for
迴圈來迭代:
>>> for n in fib(6):
... print(n)
...
1
1
2
3
5
8
執行截圖如下:
注意:
用for
迴圈呼叫generator時,我們取不到generator的return
語句的返回值。
如果想要拿到返回值,必須捕獲StopIteration
錯誤,返回值包含在StopIteration
的value
中。改寫上面的程式:
>>> g = fib(6)
>>> while True:
... try:
... x = next(g)
... print(x)
... except StopIteration as e:
... print('Generator return value:', e.value)
... break
...
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
Generator return value: done
執行截圖如下:
迭代器:
迭代器物件從集合的第一個元素開始訪問,直到所有的元素被訪問完結束。迭代器只能前進不能回退。
同生成器類似,迭代器不要求準備好整個迭代過程中所有的元素。僅僅是在迭代至某個元素時才計算該元素,而在這之前或之後,元素可以不存在或者被銷燬。這大大節省了系統儲存空間。可以被next()
函式呼叫並不斷返回下一個值的物件稱為迭代器(Iterator)
。Iterator物件可以被next()
函式呼叫並不斷返回下一個資料,直到沒有資料時丟擲StopIteration
錯誤。
可迭代物件(Iterable
)有以下幾種:
一類是集合資料型別,如列表(list)、元組(tuple)、字典(Dictionary)、集合(set)、字串等;
一類是生成器(generator)
,包括列表生成式改寫的生成器和帶yield
的生成器函式。
前面已經說過,collections模組裡的Iterable類,使用該類的isinstance()函式可判斷一個物件是否是可迭代物件(Iterable)。
>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False
我們還可以用isinstance()
函式判斷一個物件是否是Iterator。
>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False
生成器都是Iterator
物件,但list
、dict
、str
雖然是Iterable
,卻不是Iterator
。
在Python中我們可以使用iter()函式把list
、dict
、str
等Iterable
變成Iterator。
如:
>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True
Iterator物件特性:
Iterator物件可以被next()
函式呼叫並不斷返回下一個資料,直到沒有資料時丟擲StopIteration
錯誤。這個物件的資料流可以看成一個有序序列,但我們並不知道這個序列的長度,只能不斷通過next()
函式計算出下一個資料值,且只有在需要返回下一個資料時它才會計算。這種特性使得Iterator
甚至可以表示一個無限大的資料流,例如全體自然數。
Python的for in
迴圈本質上就是通過不斷呼叫next()
函式實現的。
如:
for x in [1, 2, 3, 4, 5]:
pass
實際上完全等價於:
# 首先獲得Iterator物件:
it = iter([1, 2, 3, 4, 5])
# 迴圈:
while True:
try:
# 獲得下一個值:
x = next(it)
except StopIteration:
# 遇到StopIteration就退出迴圈
break
總結:
凡是可作用於for
迴圈的物件都是Iterable
型別;
凡是可作用於next()
函式的物件都是Iterator
型別;
集合資料型別如list
、dict
、str
等是Iterable
但不是Iterator
,不過可以通過iter()
函式獲得一個Iterator
物件。