1. 程式人生 > >生成器、裝飾器、叠代器

生成器、裝飾器、叠代器

執行 使用 三層 sta rom break 原函數 什麽 一個數

一、生成器

本來我們可以通過列表生成直接生成一個列表,為什麽要用生成器呢?我們都知道列表的數據會被全部加在到內存,然後可以通過下標進行訪問數據,如果我們要的數據量非常大呢,豈不是很占用內存,所以我們需要生成器,生成器呢是我們需要哪個數據就會生成哪個數據,也就是懶加載,只有當我們使用數據的時候才會被生成,這樣就是一邊循環,一邊計算數據,這個就叫做生成器:generator。但是只能按順序生成,因為會根據上一步的返回進行生成,這樣我們就可以節約內存了。

接下來我們看一個小列子:生成列表和生成器的創建區別,創建Lg的區別僅在於最外層的[]()L是一個list,而g是一個generator

>>> l = [i*2 for i in range(10)]  #直接生成列表,會把數據全部加載
>>> l
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
>>> g = (i*2 for i in range(10)) #生成一個生成器則會返回個內存地址,然後可以通過next方法一個一個的取值,如果很多後面的值不需要使用,我們就可以節約很多內存
>>> g
<generator object <genexpr> at 0x0000010E2E6A5DB0>
>>> next(g)
0
>>> next(g) 2 >>> next(g) 4 >>> next(g) 6 >>> next(g) 8 >>> next(g) 10
>>> next(g)
12
>>> next(g)
14
>>> next(g)
16
>>> next(g)
18
>>> next(g) #每次調用next方法,就會計算出下一個元素的值,直到計算出最後一個就會跑出StopIteration的異常
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

當然除了用next獲取生成器的值,我們還可以使用for循環來獲取,所以我們不建議使用next方法,那個太麻煩了,使用for循環的話也不用擔心取得元素超出範圍拋出StopIteration異常:

>>> g = (x **2 for x in range(5))
>>> g
<generator object <genexpr> at 0x0000010E2E6A5570>
>>> for i in g:
...  print(i)
...
0
1
4
9
16

當然除了使用類似於生成列表的方式生成生成器,我們還可以使用函數來生成,我們都知道斐波拉契數列,除了前面兩個元素,每個值都是前兩個的值相加得到。

>>> def fib(n):
...  i,a,b = 0,0,1
...  while i<n:
...   print(b)
...   a,b = b,a+b
...   i += 1
...  return "done"
...
>>> fib(5)
1
1
2
3
5

如上就是斐波拉契數列的非遞歸式代碼,那麽我們如何把這個函數變成一個生成器呢,從代碼看來,我們產生的數據都是b,所以我們只要在輸出b的代碼行稍加更改動就可以。

>>> def fib(n):
...  i,a,b = 0,0,1
...  while i<n:
...    #print(b)
      yield b   #修改成yield  b即可
...   a,b = b,a+b
...   i += 1
...  return "done"
...
>>> fib(5)
<generator object fib at 0x0000010E2E6A5D00

就是這麽神奇,這就是generator的另一種生成方法,如果一個函數中包含yield關鍵字,那麽函數不再是一個普通的函數了,而是一個generator,不考慮多線程,我們都知道函數是按順序執行的,但是generator是不一樣的,在每次調用next()的時候,遇到yield就會返回,再一次執行從上次返回的yield語句處執行。

def fib(n):
    i, a, b = 0, 0, 1
    while(i<n):
        yield b
        a, b = b, a+b
        i += 1
    return done

data = fib(5)
print(data)
print(data.__next__())
print(data.__next__())
print("我來打斷一下")
print(data.__next__())

#結果如下
<generator object fib at 0x000002766915D6D0>
1
1
我來打斷一下
2

用for循環調用generator時,我們無法獲取generator的返回值,如果想要獲取到返回值,則要捕獲StopIteration異常,返回值包含在StopIteration的value中

data = fib(5)
while True:
    try:
        x = next(data)
        print("data=",x)
    except StopIteration as e:
        print("generator return value:",e.value)
        break

#程序結果是
data= 1
data= 1
data= 2
data= 3
data= 5
generator return value: done #這樣我們就可以獲取到這個返回值

二、裝飾器

接下來我們聊聊裝飾器,從字面意思來看是一個裝飾的效果,在java中也有裝飾模式,簡單來說裝飾器就是用來給函數添加新功能的,也就是函數功能的強化器,但是,裝飾器在裝飾函數的時候需要遵循幾個原則:

1、不能改變原函數的代碼

2、不能改變原函數的調用方式

3、不能改變原函數原有的結果

在聊裝飾器前我們簡單了解下高階函數和嵌套函數:

那什麽是高階函數呢?我們可以這麽理解,就是把一個函數a作為另一個函數b的參數進行傳遞,把函數當做變量來傳遞(我們可以理解函數即‘變量’),則函數b則是高階函數:

def func(x, y, f):
    print(f(x)+f(y))

func(-3,9,abs)

#執行結果
12

那什麽是嵌套函數呢?嵌套函數並不是一個函數裏面調用另一個函數,而是在定義函數的時候在其裏面再定義函數的形式:

def func(x):
    def func1(y):
        print(y)
    func1(x)

func(4)
#執行結果是
4

好了,現在進去主題,聊聊我們的裝飾器,來看一段代碼(使用高階函數)

import time
def bar(): #原函數,也就是要被裝飾的函數
    time.sleep(1)
    print(in the bar)

def test(f):
    start_time=time.time()
    f()
    stop_time=time.time()
    print("the f run time is %s" %(stop_time-start_time))

bar()
print("分割線".center(50,-))
test(bar)

#程序結果如下
in the bar
-----------------------分割線------------------------
in the bar
the f run time is 1.0021047592163086

Process finished with exit code 0

從上面結果看來,我們確實增強了函數bar的功能,但是不符合裝飾的一個原則,改變了調用方式,接下來我們處理這個問題。當然我們也可以使用bar=test(bar)的方式重新給了bar函數,也可以通過bar()的方式調用,也能達到了裝飾的效果,但是這樣我們沒法傳參數,接下來我們看一段代碼(嵌套函數出場了)

import time
#其實就是利用嵌套函數+高階函數連用
def timer(func): #timer(test1) func=test1 def deco(*args,**kwargs): #通過可變參數解決相對應的原函數傳來的參數問題,這樣就很靈活 start_time=time.time() func(*args,**kwargs) #run test1() stop_time = time.time() print("the func run time is %s" %(stop_time-start_time)) return deco @timer #這個相當於test1=timer(test1) def test1(): time.sleep(1) print(in the test1) @timer # test2 = timer(test2) = deco test2(name) =deco(name) def test2(name,age): print("test2:",name,age) test1() test2("HZhuizai",22) #程序執行結果如下 in the test1 the func run time is 1.0001072883605957 test2: HZhuizai 22 the func run time is 0.0

是不是這個裝飾器很完美了,符合了三個原則,但是如果我們有一個需求就是對不同的兩個函數有不一樣的裝飾呢,那我們該怎麽實現呢?

import time
user,passwd = alex,abc123
def auth(auth_type):
    def outer_wrapper(func):
        def wrapper(*args, **kwargs):
            print("wrapper func args:", *args, **kwargs)
func()
if auth_type == "local": print("local 模式驗證") elif auth_type == "ldap": print("ldap 模式驗證") return wrapper return outer_wrapper @auth(auth_type="local") # home = wrapper() def home(): print("welcome to home page") return "from home" @auth(auth_type="ldap") def bbs(): print("welcome to bbs page") home() #wrapper() print("分隔符".center(50,-)) bbs() #程序執行結果 wrapper func args:
welcome to home page
local 模式驗證
-----------------------分隔符------------------------
wrapper func args:
welcome to bbs page
ldap 模式驗證

為了能夠通過裝飾器傳遞參數,我們又在外層套了一個函數,可以歸納出裝飾就是高階函數和嵌套函數的組合,第一層為裝飾次傳遞參數,第二層是高階函數,把要裝飾的函數作為變量傳遞進去,第三層則是我們原函數的參數,這樣我們能完美的裝飾原函數,挺高函數的功能。

三、叠代器

我們知道可以作用於for循環的數據有列表、字典、元組、集合、字符串,這些都是集合類型,當然還有我們的生成器也可以作用於for循環,這些可以直接作用於for循環的對象統稱為叠代對象:Iterable,我們可以使用isinstance()判斷一個對象是否是Iterable對象。

>>> from collections import Iterable
>>> isinstance([],Iterable)
True
>>> isinstance("",Iterable)
True

可以被next()函數調用並且不斷返回下一個值的對象為叠代器:Iterator,同樣可以使用isinstance()判斷一個對象是否是Iterator對象。

>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([x for x in range(10)], Iterator)
False

列表、字典、元組、集合、字符串都是Iterable對象,我們可以通過iter()方法把Iterable對象轉換成Iterator對象

>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter(abc), Iterator)
True

Iterator對象表示的是一個數據流,Iterator對象可以被next()函數調用並不斷返回下一個數據,直到沒有數據時拋出StopIteration錯誤。可以把這個數據流看做是一個有序序列,但我們卻不能提前知道序列的長度,只能不斷通過next()函數實現按需計算下一個數據,所以Iterator的計算是惰性的,只有在需要返回下一個數據時它才會計算。

Iterator甚至可以表示一個無限大的數據流,例如全體自然數。而使用list是永遠不可能存儲全體自然數的。

歸納下:

凡是可作用於for循環的對象都是Iterable類型;

凡是可作用於next()函數的對象都是Iterator類型,它們表示一個惰性計算的序列;

集合數據類型如listdictstr等是Iterable但不是Iterator,不過可以通過iter()函數獲得一個Iterator對象。

生成器、裝飾器、叠代器