Python自動化開發學習4-2

列表生成式python

先看2段代碼算法

a = [ i*2 for i in range(10) ]
print(a)
#
b = []
for i in range(10):
   b.append(i*2)
print(b)
app

a和b的效果同樣,可是a使用的代碼更加簡潔ide

列表生成式也可使用函數,生成更加複雜的列表函數

a = [ max(i,6) for i in range(10) ]
print(a)

上面的是鋪墊,主要講下面的生成器測試

生成器
spa

用列表生成式,咱們能夠直接建立一個列表。等列表建立完以後,咱們能夠訪問列表中的元素。可是這都得等列表生成完以後。若是列表很大很複雜,就須要耗費很長的時間和內存空間,而後咱們才能訪問列表中的元素。線程

若是列表元素能夠按照某種算法推算出來,咱們沒必要建立完列表在進行後續的操做,而是一邊循環一邊引用每個新建立的元素。在Python中,這種一邊循環一邊計算的機制,稱爲生成器:generator。code

a = [ i*2 for i in range(10) ]
for i in a:
    print(i)
b = ( i*2 for i in range(10) )  # 這個是生成器
for i in b:
    print(i)
print(type(a),type(b))

上面的代碼中,a和b的效果是同樣的。可是a的機制是先生成完整列表,再執行for循環。而b中只是記錄了一個算法,並無生成任何數據。等到for循環調用b的時候,才一邊循環一邊計算每個元素。協程

這裏咱們感受不出兩種機制的差異,可是當列表很大或者計算很複雜的狀況下,就能發現二者的差異。咱們強行來增長生成列表的時間。

import time
def f(n):
    time.sleep(1)
    return n*2
a = [ f(i) for i in range(10) ]
for i in a:
    print(i)
b = ( f(i) for i in range(10) )  # 這個是生成器
for i in b:
    print(i)

這下就發現區別了,a是等待了很長時間來生成列表,而後快速的把結果輸出。而b是計算一個元素,輸出一個元素,開始沒有很長的等待時間,可是每次輸出之間是要等待1秒來生成新的元素。

順便我看了一下二者的效率,理論上是差很少的,可是測試下來a要耗時10.02秒,b要耗時10.005秒。每次結果會不一樣,可是都在這個數字級別。

import time
def f(n):
    time.sleep(1)
    return n*2
t = time.time()
a = [ f(i) for i in range(10) ]
for i in a:
    print(i)
print(time.time()-t)
t = time.time()
b = ( f(i) for i in range(10) )  # 這個是生成器
for i in b:
    print(i)
print(time.time()-t)

看來使用生成器,也是一個更加效率的方法。

生成器是一邊循環一邊計算的,全部的元素須要一個一個計算出來。只能一個一個的計算出來,而且只記住了當前的位置,在當前位置你只能取到下一個值,不能退回去,也不能跳過。

因此,生成器只有一個方法.__next__

b = ( i*2 for i in range(10) )
print(b.__next__())
print(b.__next__())
print(b.__next__())
print(b.__next__())
print(b.__next__())
print(b.__next__())
print(b.__next__())
print(b.__next__())
# 超出範圍會報錯

上面好LOW,通常都用循環,__next__用的不是不少。

在繼續以前,先用函數寫一個斐波那契數列。

斐波那契數列指的是這樣一個數列 1, 1, 2, 3, 5, 8, 13, 21,這個數列從第3項開始,每一項都等於前兩項之和。

def fib(n):
    i,a,b = 0,0,1
    while i < n:
        print(b)
        a,b = b,a+b
        i += 1
    return "結束"
fib(10)

把上面的函數的print(b)替換成yield b,就實現了用函數作了一個生成器。

def fib(n):
    i,a,b = 0,0,1
    while i < n:
        #print(b)
        yield b  # 替換成這句
        a,b = b,a+b
        i += 1
    return "結束"
f = fib(10)
print(type(f))  # f的數據類型是generator

這是定義生成器的另外一種方法。若是一個函數定義中包含yield關鍵字,那麼這個函數就再也不是一個普通函數,而是一個生成器。

生成器在語句執行遇到yield的時候會返回,可是會記住當前的位置,若是你使用.__next__()則會在以前的位置繼續執行。也就是生成器在執行而且返回以後,並無徹底結束。跳出生成器後能夠正常執行別的語句,在須要的時候再從以前生成器返回的位置繼續執行下一次循環。這樣的話生成器最後的return就沒有意義了。接着看,咱們先試着打印出生成器中的全部元素。

def fib(n):
    i,a,b = 0,0,1
    while i < n:
        #print(b)
        yield b
        a,b = b,a+b
        i += 1
    return "結束"
f = fib(10)
print(type(f))
print(f.__next__())
print("你能夠隨時插入你的語句")
print(f.__next__())
print("生成器會記住以前的位置繼續循環")
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
print("打了那麼多就是要超出限制")
print(f.__next__())
print(f.__next__())

若是超出了繼續取,就會報錯。仔細看一下報錯的內容,最後的StopIteration:後的內容就是你return的內容。咱們能夠用try來捕獲這個錯誤從而獲取return的值。try還沒講到,後面應該會細講。

def fib(n):
    i,a,b = 0,0,1
    while i < n:
        #print(b)
        yield b
        a,b = b,a+b
        i += 1
    return "結束"
f = fib(10)
while 1:
    try:
        x = next(f)  # x = f.__next__()
        print(x)
    except StopIteration as e:
        print("返回值是:",e.value)
        break

這裏x=next(f)和x=f,__next__()效果同樣。這裏上課沒講,暫時沒發現有什麼區別。

經過yield還能夠實現單線程下的並行效果,這個不叫並行,叫協程。這裏好煩,直接抄老師的例子了。

import time
def consumer(name):
    print("%s 準備吃包子啦!" %name)
    while True:
       baozi = yield  # 這裏中斷,經過send傳值進來繼續執行
       print("包子[%s]來了,被[%s]吃了!" %(baozi,name))
def producer(name):
    c = consumer('A')  # 定義了一個consumer('A'),可是並無運行
    c2 = consumer('B')  # 定義了一個consumer('B')
    c.__next__()  # consumer('A')啓動運行,運行到yield以前,打印"準備吃包子啦"
    c2.__next__()  # consumer('B')啓動運行
    print("老子開始準備作包子啦!")
    for i in range(10):
        time.sleep(1)
        print("作了2個包子!")
        c.send(i)  # send是給yield傳值
        c2.send(i)
producer("C")

上面的例子裏.send(i)以前沒提過,和.__next__()同樣,可是sent能夠傳一個值回去。

上面的例子就是producer啓動之後,又啓動了2個consumer。consumer運行到yield中斷,等待。producer將值經過send傳給consumer後,consumer就執行一次循環,而後再中斷,等待新的值傳入。

迭代器

可直接做用於for循環的對象,統稱爲可迭代對象:Iterable

使用下面這個方法能夠判斷一個對象是否迭代

from collections import Iterable
print(isinstance('',Iterable))  # 字符串可迭代
print(isinstance(123,Iterable))  # 數字不可迭代
print(isinstance((),Iterable))
print(isinstance([],Iterable))
print(isinstance({},Iterable))
print(isinstance((x for x in range(10)),Iterable))  # 這個是生成器,可迭代

生成器不但能夠做用於for循環,還能夠next,不斷調用返回下一個值。

能夠被__next__()調用並不斷返回下一個值的對象稱爲迭代器:Iterator

使用下面的方法再判斷一下以前的對象是不是迭代器

from collections import Iterator
print(isinstance('',Iterator))
print(isinstance(123,Iterator))
print(isinstance((),Iterator))
print(isinstance([],Iterator))
print(isinstance({},Iterator))
print(isinstance((x for x in range(10)),Iterator))

只有最後一個能夠__next__(),只有最後一個是True,是迭代器。

上面的這些可迭代對象,目前都不是迭代器。經過iter()就能夠把這些可迭代對象變成迭代器。

from collections import Iterator
a = [1,2,3,4,5,6]
print(isinstance(a,Iterator))
b = iter(a)
print(isinstance(b,Iterator))
print(b.__next__())
print(b.__next__())
print(b.__next__())

Python的Iterator對象表示的是一個數據流,Iterator對象能夠被next()函數調用並不斷返回下一個數據,直到沒有數據時拋出StopIteration錯誤。能夠把這個數據流看作是一個有序序列,但咱們卻不能提早知道序列的長度,只能不斷經過next()函數實現按需計算下一個數據,因此Iterator的計算是惰性的,只有在須要返回下一個數據時它纔會計算。這樣,迭代器甚至能夠表示一個無限大的數據流。

咱們的for循環,本質上就是經過不斷調用next來實現的。

相關文章
相關標籤/搜索