1. 程式人生 > >列表解析式、生成器、迭代器及可迭代物件的區別和應用

列表解析式、生成器、迭代器及可迭代物件的區別和應用

導文

  • 語法糖(Syntactic sugar)
  • 列表生成式(list comprehension)
  • 生成器(generator)
  • 迭代器(iterator)
  • 可迭代物件(iterable)
  • Iterable、Iterator與Generator之間的關係

語法糖

語法糖(Syntactic sugar),是由Peter J. Landin(和圖靈一樣的天才人物,是他最先發現了Lambda演算,由此而創立了函數語言程式設計)創造的一個詞語,它意指那些沒有給計算機語言新增新功能,而只是對人類來說更“甜蜜”的語法。語法糖往往給程式設計師提供了更實用的編碼方式,有益於更好的編碼風格,更易讀。
在python語言中語法糖有三元表示式、列表生成式、列表生成器、迭代器等等,具體可參考如下部落格:

 https://segmentfault.com/a/1190000006261012

解析式通用語法

For constructing a list, a set or a dictionary Python provides special syntax called “displays”, each of them in two flavors:
為構造一個列表、集合或者字典,python提供了稱謂”顯式”的特殊語法,每種語法都有兩種形式
1、either the container contents are listed explicitly
容器內容被明確列出(即普通常用的列表)
2、they are computed via a set of looping and filtering instructions, called a comprehension
它們通過一組迴圈和過濾指令來計算,稱為生成式
Common syntax elements for comprehensions are:
語法格式如下:
comprehension ::= expression comp_for
comp_for ::= “for” target_list “in” or_test [comp_iter]
comp_iter ::= comp_for | comp_if
comp_if ::= “if” expression_nocond [comp_iter]
The comprehension consists of a single expression followed by at least one for clause and zero or more for or if clauses.
生成式是由單個表示式

後緊跟至少一個for語句和多個或0個if語句

列表生成式

列表定義

A list display is a possibly empty series of expressions enclosed in square brackets:
list_display ::= “[” [starred_list | comprehension] “]”

生成式語法

基礎語法格式

[expr for iter_var in iterable] 即 [返回值 for 元素 in 可迭代物件]
工作過程:
- 迭代iterable中的每個元素;
- 每次迭代都先把結構賦值給iter_var,然後通過exp得到一個新的返回值;
- 最後將返回值形成一個新的列表。
程式碼示範:

#!/bin/python3
#-*- coding: UTF-8 -*-
lst = [x*x for x in range(1,6)]
print(lst)
執行結果:
[1, 4, 9, 16, 25]

條件語法格式

[expr for item in iterable if cond1 if cond2]
工作過程:

  • 迭代iterable中的每一個元素,然後對每個元素進行if條件判斷,當有多個if時,if條件相當於if cond1 and if cond 2
  • 將迭代的結果複製給item,然後通過expr表示式計算出返回值
  • 將返回值形成新的列表
    程式碼示範:
#!/bin/python3
#-*- coding: UTF-8 -*-
lst = [x for x in range(1,20) if x>=10 if x%2==0]
print(lst)
執行結果:
[10, 12, 14, 16, 18]

巢狀迴圈語法

[expr for i in iterable1 for j in iterable2 ]
工作過程:

  • 迭代iterable1中的第一個元素後,進入下一輪for迴圈迭代iterable2中的每一元素,interable2迴圈完成後,再次進入iterable1中的第二個元素,以此類推。
  • 把迭代結果賦值給iter_var,榮光expr得到返回值
  • 最後將返回值形成新的物件list
    程式碼示範:
#!/bin/python3
#-*- coding: UTF-8 -*-
lst0 = [(x,y) for x in 'abc' for y in range(2)]
lst1 = [[x,y] for x in 'abc' for y in range(2)]
lst2 = [{x,y} for x in 'abc' for y in range(2)]
print(lst0)
print(lst1)
print(lst2)
執行結果:
[('a', 0), ('a', 1), ('b', 0), ('b', 1), ('c', 0), ('c', 1)]
[['a', 0], ['a', 1], ['b', 0], ['b', 1], ['c', 0], ['c', 1]]
[{0, 'a'}, {1, 'a'}, {0, 'b'}, {1, 'b'}, {0, 'c'}, {1, 'c'}]

列表生成式經典習題

返回1-10平方的列表

程式碼示範:

#!/bin/python3
#-*- coding: UTF-8 -*-
lst = [x**2 for x in range(1,11)]
print(lst)
執行結果:
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

有一個列表lst = [1,4,9,16,2,5,10,15]

生成新列表,要求新列表元素是lst相鄰2項的和
程式碼示範:

#!/bin/python3
#-*- coding: UTF-8 -*-
lst = [1,4,9,16,2,5,10,15]
lstnew = [lst[i]+lst[i+1] for i in range(len(lst)-1)]
print(lstnew)
執行結果:
[5, 13, 25, 18, 7, 15, 25]

列印九九乘法表

考點
- 嚴格按照工作過程和列表解析式的定義,套用至少一個for迴圈或多個for迴圈
- for迴圈必須是連續在一起的,for迴圈不能分開
正確示範程式碼:

#!/bin/python3
#-*- coding: UTF-8 -*-
lst = [print("{}*{}={:<3}{}".format(j,i,j*i,"\n" if i==j else ""),end="") for i in range(1,10) for j in range(1,i+1)]
print(lst)
執行結果:
1*1=1  
1*2=2  2*2=4  
1*3=3  2*3=6  3*3=9  
1*4=4  2*4=8  3*4=12 4*4=16 
1*5=5  2*5=10 3*5=15 4*5=20 5*5=25 
1*6=6  2*6=12 3*6=18 4*6=24 5*6=30 6*6=36 
1*7=7  2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49 
1*8=8  2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64 
1*9=9  2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81

切忌for j迴圈寫入format中,如果將j迴圈寫入format中時,違反了程式碼語法:[expr for iter_val in iterable for iter_val in iterable],若違反程式碼語法肯定報錯,根據工作過程知道,首先進行迴圈i,迭代到1時,返回給expr表示式print(),print的返回值構成新的列表。
另外:最後一個for迴圈表示要出現的次數,下面錯誤程式碼中示範的案例表明只會出現10個相乘的數,因此和題意完全不符,
錯誤示範程式碼:

#!/bin/python3
#-*- coding: UTF-8 -*-
lst = [print("{}*{}={:<3}{}".format(j,i,j*i,"\n" if i==j else ""),end="") for i in range(1,10) for j in range(1,i+1)]
print(lst)
執行結果:
SyntaxError: Generator expression must be parenthesized if not sole argument
SyntaxError:如果不是唯一引數,則必須將生成器表示式加括號

“0001.abadicddws”是ID格式

要求ID格式是以點號分割,左邊是4為從1開始的整數,右邊是10位隨機小寫英文字母,請依次生成前100個ID的列表

#!/bin/python3
#-*- coding: UTF-8 -*-
import random
string = "abcdefghigklmnopqrstuvwsyz"
lst = ["{:04}.{}".format(i,"".join(string[random.randint(0,25)] for j in range(10))) for i in range(1,4)]
print(lst)
執行結果:
['0001.mgdpsgtcqa', '0002.kduzdfncds', '0003.itohqzghzk']

程式碼對比說明問題

#巢狀正確程式碼
string = "abcdefghigklmnopqrstuvwsyz"
lst = ["{:04}.{}".format(i,"".join(string[random.randint(0,25)] for j in range(10))) for i in range(1,4)]
print(lst)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#巢狀非正確程式碼
lst = ["{}*{}".format(i,j for j in range(1,i+1)) for i in range(1,10)]
print(lst)
#巢狀說明問題
當生成i*j個的expr表示式時,巢狀for時,兩個for時兄弟並聯關係
當生成i個expr表示式的值時,在expr表示式內可以可以巢狀for迴圈,但是expr表示式內的for迴圈和語法糖中的for迴圈毫無關係

列表高階函式經典

將字典轉換成元組組成的列表

lst={‘Tom’:15,’Jerry’:18,’Peter’:13}

#!/bin/python3
#-*- coding: UTF-8 -*-
dic = {'Tom':15,'Jerry':18,'Peter':13}
lst = [(x,y) for x,y in dic.items()]
print(lst)
執行結果:
[('Jerry', 18), ('Tom', 15), ('Peter', 13)]

把列表中所有字元轉換小寫,非字串元素保留原樣

lst = [‘TOM’,’Peter’,10,’Jerry’]

#!/bin/python3
#-*- coding: UTF-8 -*-
lst = ['TOM','Peter',10,'Jerry']
lst0 = [x.lower() if isinstance(x,str) else x for x in lst]
lst1 = list(map(lambda x:x.lower() if isinstance(x,str) else x,lst))
print(lst0)
print(lst1)
執行結果:
['tom', 'peter', 10, 'jerry']
['tom', 'peter', 10, 'jerry']

把列表中所有的字串轉換小寫,非字串元素移除

lst = [‘TOM’,’Peter’,10,’Jerry’]

#!/bin/python3
#-*- coding: UTF-8 -*-
lst = ['TOM','Peter',10,'Jerry']
lst0 = [x.lower() for x in lst if isinstance(x,str)]
lst1 = list(map(lambda x:x.lower(),filter(lambda x: isinstance(x,str),lst)))
print(lst0)
print(lst1)
執行結果:
['tom', 'peter', 'jerry']
['tom', 'peter', 'jerry']

生成器(generator)

生成器定義

A function which returns a generator iterator. It looks like a normal function except that it contains yield expressions for producing a series of values usable in a for-loop or that can be retrieved one at a time with the next() function.
返回生成器迭代器的函式。 它看起來像一個普通的函式,只是它包含yield表示式,用於生成一系列可用於for-loop的值,或者可以使用next()函式一次檢索一個值。

生成器

generator iterator
An object created by a generator function,Each yield temporarily suspends processing, remembering the location execution state (including local variables and pending try-statements). When the generator iterator resumes, it picks-up where it left-off (in contrast to functions which start fresh on every invocation).
生成器迭代器是通過生成器生成的一個物件,每次遇到yield時會暫停生產值並記住位置,當再次遇到迭代時它會在從暫停的位置重新開始,
其實生成器就是迭代器中的一種表現形式但是不同的物件,而且都是可迭代物件,後面有實驗證明[在迭代器判斷中]

生成器的構成

生成器的構成是通過兩種方式:
- 使用類似列表的方式生成
具體資訊如下:
1.語法格式(返回值 for 元素 in 可迭代物件 if條件)
2.列表解析式的中括號換成小括號即可
3.返回一個生成器
- 使用包含yield的函式來生成
通常情況下對應簡單的列表等採用列表生成器,若遇到複雜的計算值採用yield函式構成。

生成器的執行過程與特性

執行過程:
在執行過程中,遇到yield關鍵字就會終端執行,下次繼續從中斷位置開始執行。
特性:
1.與列表解析式截然不同,列表解析式是立即返回一個完整的列表,生成器表示式是按需計算,惰性求值,需要是才進行求值;
2.遇到yield記錄當前位置,下次從記錄位置繼續執行
3.從前到後走完一遍後,不能回頭

生成器值的訪問方式

  • 通過內建next()方法
  • 使用迴圈方式進行迭代
  • 呼叫生成器物件send()方法

例項說明

生成器表訪問

程式碼及特性示範1:

#!/bin/python3
#-*- coding: UTF-8 -*-
g = ("{:04}".format(i) for i in range(1,6))
print(next(g))
for x in g:
    print(x)
print('~~~~~~~~~~~~')
for x in g:
    print(x)
執行結果:
0001
0002
0003
0004
0005
~~~~~~~~~~~~

生成器表示式返回值構成列表

程式碼示範2:
expr表示式返回值構成列表或生成器,若無返回值時反回NoneTpye

#!/bin/python3
#-*- coding: UTF-8 -*-
#val的值是什麼?
#val=first+second語句之後能否再next()?
g = (print('{}'.format(i+1)) for i in range(2))
first = next(g)
second = next(g)
val = first + second
print(val)
print(next(g))
執行結果:
1
2
Traceback (most recent call last):
  File "./gen.py", line 9, in <module>
    val = first + second
TypeError: unsupported operand type(s) for +: 'NoneType' and 'NoneType'

生成器的next不能回頭

程式碼示範3:

#!/bin/python3
#-*- coding: UTF-8 -*-
#val的值是什麼?
#val=first+second語句之後能否再next()?
g = (x for x in range(10) if x % 2)
#取奇數1,3,5,7,9 first=1 second=3 val=4 下一個為5
first = next(g)
second = next(g)
val = first + second
print(val)
print(next(g))
執行結果:
4
5

寫生成器的兩種方式

生成器表示式

g = (x for x in range(10))

yield語句

def func():
yield

列表解析式和生成器對比

計算方式

生成器表示式延遲計算,列表解析式立即計算

記憶體佔用

  • 但從返回值本身來說,生成器表示式省記憶體,列表解析式返回新的列表
  • 生成器沒有資料,記憶體佔用極少
  • 列表解析式構造新的列表需要佔用記憶體

計算速度

  • 單看計算時間,生成器表示式耗時非常短,列表解析式耗時長
  • 生成器本身並沒有返回值,只返回一個生成器物件,雖然是返回值但是累加起來佔用記憶體和列表生成式幾乎相等
  • 列表解析式構造並返回一個新的列表

實驗論證

#!/bin/python3
#-*- coding: UTF-8 -*-
import datetime
import sys
start = datetime.datetime.now()
lst = [ x for x in range(10000000) ]
delta = (datetime.datetime.now() - start).total_seconds()
print("列表解析式耗時:{}".format(delta))
print("列表解析式耗記憶體:{}".format(sys.getsizeof(lst)))
start0 = datetime.datetime.now()
lst = ( x for x in range(10000000) )
delta0 = (datetime.datetime.now() - start0).total_seconds()
print("列表生成式耗時:{}".format(delta0))
print("列表生成式耗記憶體:{}".format(sys.getsizeof(lst)))
start1 = datetime.datetime.now()
def test_gen(start,end):
    for x in range(start,end):
        yield x
lst =test_gen(0,10000000)
delta1 = (datetime.datetime.now() - start1).total_seconds()
print("列表生成器函式耗時:{}".format(delta1))
print("列表生成器函式耗記憶體:{}".format(sys.getsizeof(lst)))
執行結果:
列表解析式耗時:0.328316
列表解析式耗記憶體:81528056
列表生成式耗時:0.104485
列表生成式耗記憶體:88
列表生成器函式耗時:1.2e-05
列表生成器函式耗記憶體:88

可迭代物件

可直接用於for迴圈的物件統稱為可迭代物件(Iterable)
判斷是否是可迭代物件,用函式isinstance()

#!/bin/python3
#-*- coding: UTF-8 -*-
from collections import Iterable
print(isinstance("abc",Iterable))
print(isinstance("[]",Iterable))
print(isinstance("{}",Iterable))
print(isinstance("()",Iterable))
print(isinstance("(x,for x in range(2))",Iterable))
執行結果:
True
True
True
True
True

迭代器

迭代器定義

An object representing a stream of data. Repeated calls to the iterator’s next() method (or passing it to the built-in function next()) return successive items in the stream. When no more data are available a StopIteration exception is raised instead. At this point, the iterator object is exhausted and any further calls to its next() method just raise StopIteration again.
表示資料流的物件。 重複呼叫迭代器的next ()方法(會返回流中的連續項。直到沒有資料可以返回時丟擲StopIteration異常錯誤。可以把這個資料流看做一個有序序列,但我們無法提前知道這個序列的長度。同時,Iterator的計算是惰性的,只有通過next()函式時才會計算並返回下一個資料。

迭代器判斷

通過定義及判斷可知,生成器也是迭代器,但是不是同一個物件,通過type返回值不一樣

#!/bin/python3
#-*- coding: UTF-8 -*-
from collections import Iterator
print(isinstance("abc",Iterator))
print(isinstance("()",Iterator))
print(isinstance((x for x in range(2)),Iterator))

Iterable、Iterator和Generator的關係

  • 生成器物件即是可迭代物件,也是迭代器,因為生成器既可以用for迴圈求值,也可以通過next()求值,直到丟擲StopIteration時無法繼續生成新值
  • 迭代器物件一定時可迭代物件,可迭代物件不一定為迭代器,因為迭代器、生成器、可迭代物件都可以用for迴圈迭代求值,只有生成器和迭代器可以被next()方法求值