1. 程式人生 > >Python3 從零單排6_裝飾器&生成器&迭代器

Python3 從零單排6_裝飾器&生成器&迭代器

1.裝飾器

  明月裝飾了你的窗子,你裝飾了我的夢。所謂裝飾就是不改變原來實物本身,只是在事物之外給它加以裝飾。

  在程式設計裡一樣,因為專案會一直有優化、更新,所以可能會對以前的功能進行優化,那麼開發的原則是“開放-封閉”。開放:允許在原有的功能上擴充套件功能;封閉:不允許修改原始碼。所以有了裝飾器,在不改變原始碼的情況下,增加功能。

  裝飾器原理:

# 現在已經寫好了一個函式,現在需要得到它的執行時間
import time,random


def sort_lis(n):
    lis = [i for i in range(n)]
    random.shuffle(lis) 
#打亂列表順序 lis.sort() # n越大耗時越多,排序是cpu密集型,消耗cpu時間碎片很多 print("done!") start_time = time.time() sort_lis(1000000) end_time = time.time() rum_time = end_time - start_time print(rum_time) #完美,我們拿到這個時間,但是如果有很多個這樣的函式需要獲得執行時間,每次呼叫都這樣去加程式碼顯然很low,而且違反了開發專案的封閉原則。

  這個時候會思考,那麼我把這個獲取時間的程式碼,寫成一個函式,調sort_lis的時候,直接調這個函式就好了,但是要怎麼寫呢,因為sort_lis要在你的函式中執行才能拿到時間啊,於是寫出了這樣的程式碼

import time,random

def get_run_time(func):
    start_time = time.time()
    res = func()
    end_time = time.time()
    rum_time = end_time - start_time
    print(rum_time)
    return res


def sort_lis():
    lis = [i for i in range(100000)]
    random.shuffle(lis) #打亂列表順序
    lis.sort()  # n越大耗時越多,排序是cpu密集型,消耗cpu時間碎片很多
print("done!") get_run_time(sort_lis)

  哈哈哈,太機智了,直接就拿到了這個執行時間,但是老鐵,1你改變了函式的呼叫方式,2.有沒發現萬一sort_lis這個函式有傳參怎麼辦,再這樣寫是不是涼涼了?於是想到了更進一步的辦法

import time,random

def get_run_time(func):
    def wrapper():
        start_time = time.time()
        res = func()
        end_time = time.time()
        rum_time = end_time - start_time
        print(rum_time)
        return res
    return wrapper

def sort_lis():
    lis = [i for i in range(10000)]
    random.shuffle(lis) #打亂列表順序
    lis.sort()  # n越大耗時越多,排序是cpu密集型,消耗cpu時間碎片很多
    print("done!")

new_func = get_run_time(sort_lis)
new_func()

  這回你想沒啥問題了吧,但是還是上面兩點沒達到,別人之前是sort_lis()呼叫,現在變成了new_func()呼叫,函式名都被你改了,以前的那麼多程式碼都一個個去改麼,顯然又懵了,於是你想到了我直接用原函式變數名來命名不就好了麼?

import time,random

def get_run_time(func):
    def wrapper():
        start_time = time.time()
        res = func()
        end_time = time.time()
        rum_time = end_time - start_time
        print(rum_time)
        return res
    return wrapper

def sort_lis():
    lis = [i for i in range(10000)]
    random.shuffle(lis) #打亂列表順序
    lis.sort()  # n越大耗時越多,排序是cpu密集型,消耗cpu時間碎片很多
    print("done!")

sort_lis = get_run_time(sort_lis)
sort_lis()

  看這樣我就實現了吧,既不改變原函式程式碼,也沒改變原來的呼叫方式,只是加了一句 sort_lis = get_run_time(sort_lis) 就搞定了,沒錯,這就是裝飾器的原理了,不過一般不這樣寫,用@,俗稱語法糖,如下:

import time,random

def get_run_time(func):
    def wrapper():
        start_time = time.time()
        res = func()
        end_time = time.time()
        rum_time = end_time - start_time
        print(rum_time)
        return res
    return wrapper

@get_run_time
def sort_lis():
    lis = [i for i in range(10000)]
    random.shuffle(lis) #打亂列表順序
    lis.sort()  # n越大耗時越多,排序是cpu密集型,消耗cpu時間碎片很多
    print("done!")

sort_lis()

  那麼問題來了,要是被裝飾函式有傳參,上面的這個不是搞不定了嗎?

  被裝飾函式有傳參:

import time,random

def get_run_time(func):
    def wrapper(*args,**kwargs):
        start_time = time.time()
        res = func(*args,**kwargs)
        end_time = time.time()
        rum_time = end_time - start_time
        print(rum_time)
        return res
    return wrapper

@get_run_time
def sort_lis(n):
    lis = [i for i in range(n)]
    random.shuffle(lis) #打亂列表順序
    lis.sort()  # n越大耗時越多,排序是cpu密集型,消耗cpu時間碎片很多
    print("done!")

sort_lis(10000)

  裝飾器本身帶有傳參:等於是把上面的裝飾器再包了一層。
  比如這裡你想讓測試人員嚇一跳,你可以這樣弄,一個功能相應時間多加sec秒,然後下次提測把不傳這個引數,相應時間瞬間就上去了,讓測試覺得你很厲害,嘿嘿。

import time,random

def timer(sec=''):
    def get_run_time(func):
        def wrapper(*args, **kwargs):
            start_time = time.time()
            if sec:
                time.sleep(sec)
            res = func(*args, **kwargs)
            end_time = time.time()
            rum_time = end_time - start_time
            print(rum_time)
            return res
        return wrapper
    return get_run_time

@timer(2)
def sort_lis(n):
    lis = [i for i in range(n)]
    random.shuffle(lis) #打亂列表順序
    lis.sort()  # n越大耗時越多,排序是cpu密集型,消耗cpu時間碎片很多
    print("done!")

sort_lis(10000)

 

2.生成器

  先來看下列表生成式:就是根據一個規則,生成一個列表。

lis = [i for i in range(10)]
new_lis = [x**x for x in lis]
print(lis,new_lis)

  生成式很好用,但是當列表元素太多的時候,記憶體會吃不消,有沒有一種方法,我只做一個生產元素的模型,需要元素了我再去模型裡拿,每取一次給我生成一個元素,這樣就不佔記憶體,而且還達到了目的。是的,這個模型就是生成器。

  生成器的建立和呼叫:

# 生成器的第一種建立方式:就是把上面的列表生成式的[]符號,改成(),就是一個生成器了:
new_lis = (x**x for x in range(10))
print(new_lis)
# <generator object <genexpr> at 0x0000000004034938> 這就是我們的生成器了

# 生成器的第二種建立方式:函式里加上 yield,函式就變成了一個生成器
def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        # print(b)
        yield  b
        a, b = b, a + b
        n = n + 1
    return 'done'
new_fib = fib(2)
print(new_fib)
# <generator object fib at 0x00000000040149E8>  這就是我們函式生成的生成器了

# 生成器的三種呼叫方式:
print(next(new_fib))  # 直接next可以一次次的獲取生成器生成的元素,當取完最後一個元素時,會報錯:StopIteration: done
print(new_fib.send("hello")) # 同next,只是可以傳遞一個訊號到生成器內部。
for i in new_fib:  # 這個呼叫和迴圈去可迭代物件一樣,一般是用這種方法呼叫生成器,調完結束不會報錯。
    print(i)

  上面有說道訊號,這個訊號有啥用呢?

def fib2(max):
    n, a, b = 0, 0, 1
    while n < max:
        # print(b)
        single = yield  b  #拿到send過來的訊號
        if single == "stop": #如果訊號說stop,就跳出迴圈,不再生成元素
            break
        a, b = b, a + b
        n = n + 1
    return 'done'
new_fib = fib2(7)
print(next(new_fib))
print(new_fib.send("hello"))
print(new_fib.send("stop"))  #生成器在接到這個訊號的時候,直接不再返回元素,拋錯:StopIteration: done

 

3.迭代器

  我們已經知道,可以直接作用於for迴圈的資料型別有以下幾種:一類是集合資料型別,如list、tuple、dict、set、str等;一類是generator,包括生成器和帶yield的generator function。

  這些可以直接作用於for迴圈的物件統稱為可迭代物件: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

  而生成器不但可以作用於for迴圈,還可以被next()函式不斷呼叫並返回下一個值,直到最後丟擲StopIteration錯誤表示無法繼續返回下一個值了。
  *可以被next()函式呼叫並不斷返回下一個值的物件稱為迭代器:Iterator。可以使用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。
  把list、dict、str等Iterable變成Iterator可以使用iter()函式:

isinstance(iter([]), Iterator)
# True
isinstance(iter('abc'), Iterator)
# True

  可能會有疑問,為什麼list、dict、str等資料型別不是Iterator?
  這是因為Python的Iterator物件表示的是一個資料流,Iterator物件可以被next()函式呼叫並不斷返回下一個資料,直到沒有資料時丟擲StopIteration錯誤。可以把這個資料流看做是一個有序序列,但我們卻不能提前知道序列的長度,只能不斷通過next()函式實現按需計算下一個資料,所以Iterator的計算是惰性的,只有在需要返回下一個資料時它才會計算。
  Iterator甚至可以表示一個無限大的資料流,例如全體自然數。而使用list是永遠不可能儲存全體自然數的。

  小結:
  凡是可作用於for迴圈的物件都是Iterable型別;
  凡是可作用於next()函式的物件都是Iterator型別,它們表示一個惰性計算的序列;
  集合資料型別如list、dict、str等是Iterable但不是Iterator,不過可以通過iter()函式獲得一個Iterator物件。
  Python3的for迴圈本質上就是通過不斷呼叫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