1. 程式人生 > >Python切片、迭代、列表生成式、生成器、迭代器

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支援切片操作的資料型別有listtuplestringunicoderange;

切片返回的結果型別與原物件型別一致;

切片不會改變原物件,而是重新生成了一個新的物件。

對元組(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錯誤,返回值包含在StopIterationvalue中。改寫上面的程式:

>>> 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物件,但listdictstr雖然是Iterable,卻不是Iterator

在Python中我們可以使用iter()函式把listdictstrIterable變成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型別;

集合資料型別如listdictstr等是Iterable但不是Iterator,不過可以通過iter()函式獲得一個Iterator物件。