天天看點

python中“生成器”、“疊代器”、“閉包”、“裝飾器”的深入了解

一、生成器

1、什麼是生成器?

在python中,一邊循環一邊計算的機制,稱為生成器:generator.

2、生成器有什麼優點?

  1、節約記憶體。python在使用生成器時對延遲操作提供了支援。所謂延遲,是指在需要的時候才産生結果,而不是立即産生結果。這樣在需要的時候才去調用結果,而不是将結果提前存儲起來要節約記憶體。比如用清單的形式存放較大資料将會占用不少記憶體。這是生成器的主要好處。比如大資料中,使用生成器來調取資料結果而不是清單來處理資料,因為這樣可以節約記憶體。

  2、疊代到下一次的調用時,所使用的參數都是第一次所保留下的。

3、在python中建立生成器

在python中,有兩種建立生成器的方式:

  1、生成器表達式

類似與清單推導,但是,生成器傳回按需産生結果的一個對象,而不是一次建構一個結果清單。

使用清單推導,将會一次傳回所有結果:

1 li=[i**2 for i in range(5)]
2 print(li)
3 
4 [0, 1, 4, 9, 16]
5 [Finished in 0.3s]      

将清單推導的中括号,替換成圓括号,就是一個生成器表達式:

1 li=(i**2 for i in range(5))
2 print(li)
3 for x in range(5):
4     print(next(li))      

輸出結果為:

1 <generator object <genexpr> at 0x0000000001E18938>
2 0
3 1
4 4
5 9
6 16
7 [Finished in 0.3s]      

注意:建立完成生成器後,可以使用next()來調用生成器的資料結果,每調用一次傳回一個值,直到調用結束。調用結束後li中為空的,不在有任何值。要想使用它,隻能重新建立新的生成器。(生成器表達式的第四行代碼也可以改成:print(x).)

  2、生成器函數

正常函數定義,但是使用yield語句而不是return語句傳回結果。yield語句每次傳回一個結果,但每個結果中間,挂起函數的狀态,以便下次從它離開的地方繼續執行。

我們下面來看一個例子。下面為一個可以無窮生産奇數的生成器函數。

1 def odd():
 2     n=1
 3     while True:
 4         yield n
 5         n+=2
 6 odd_num=odd()
 7 count=0
 8 for o in odd_num:
 9     if count >=5:
10         break
11     print(o)
12     count += 1      

輸出結果為:

1 1
2 3
3 5
4 7
5 9
6 [Finished in 0.3s]      

上面的函數中,yield是必備的,當一個普通函數中包含yield時,系統會預設為是一個generator。

再舉一個例子。使用生成器函數來生成斐波納契數列。

1 def fib(times):
 2     n = 0
 3     a,b = 0,1
 4     while n < times:
 5         yield b
 6         a,b = b,a + b
 7         n += 1
 8     return "done"
 9 f = fib(5)
10 for x in f:
11     print(x)      

輸出結果為:

1 1
2 1
3 2
4 3
5 5
6 [Finished in 0.3s]      

生成器的兩種方法:__next__() 和 send() 方法。

其中,__next__() 方法和next的作用是一樣的。如下所示。

1 def fib(times):
 2     n=0
 3     a,b=0,1
 4     while n < times:
 5         yield b
 6         a,b=b,a+b
 7         n+=1
 8 f = fib(5)
 9 for i in range(5):
10     print(f.__next__())      

輸出結果為:

1 1
2 1
3 2
4 3
5 5
6 [Finished in 0.3s]      

從上面的程式中可以看出,f._next_() 和 next(f) 是作用是一樣的。

下面再來看看send() 方法的使用。

1 def fib(times):
 2     n = 0
 3     a,b = 0,1
 4     while n < times:
 5         temp = yield b 
 6         print(temp)
 7         a,b = b,a+b
 8         n += 1
 9 f = fib(5)
10 print(f.__next__())
11 print(f.send("haha"))
12 print(f.send("wangji"))      

輸出結果為:

1 1
2 haha
3 1
4 wangji
5 2
6 [Finished in 0.3s]      

從上面代碼可以看出:使用send()時,必須在yield前面加上一個變量,并列印這個變量。在調用send()方法時其前面需要至少使用一次next()或__next__()方法,因為生成器不可以在使用之前導入任何非None參數。由此可以知道,send()是用來向生成器中導入參數并傳回該參數的,與該參數一起傳回的還有生成器中原先儲存的資料。

4、再看生成器

前面已經對生成器有了感性的認識,我們以生成器函數為例,再來深入探讨一下python的生成器:

  1、文法上和函數類似:生成器函數和正常函數幾乎是一樣的。它們都是使用def語句進行定義,差别在于,生成器使用yield語句傳回一個值,而正常函數使用return語句傳回一個值。

  2、自動實作疊代器協定:對于生成器,python會自動實作疊代器協定,以便應用到疊代北京中(如for循環,sum函數)。由于生成器自動實作了疊代器協定,是以,我們可以調用它的next方法,并且,在沒有值可以傳回的時候,生成器自動産生StopIteration異常

  3、狀态挂起:生成器使用yield語句傳回一個值。yield語句挂起該生成器函數的狀态,保留足夠的資訊,以便以後從它離開的地方繼續執行

5、示例

我們再來看兩個生成器的例子,以便大家更好的了解生成器的作用。

首先,生成器的好處是延遲計算,一次傳回一個結果。也就是說,它不會一次生成所有的結果,這對于大資料量處理,将會非常有用。

大家可以在自己的電腦上試試下面兩個表達式,并且觀察記憶體占用情況。對于前一個表達式,我在自己的電腦上進行測試,還沒有看到最終結果電腦就已經卡死,對于後一個表達式,幾乎沒有什麼記憶體占用。

1 a=sum([i for i in range(1000000000)])
2 print(a)
3 
4 b=sum(i for i in range(1000000000))
5 print(b)      

除了延遲計算,生成器還能有效提高代碼可讀性。例如,現在有一個需求,求一段文字中,每個單詞出現的位置。

不使用生成器的情況:

1 def index_words(text):
2     result = []
3     if text:
4         result.append(0)
5     for index,letter in enumerate(text,1):
6         if letter == " ":
7             result.append(index)
8     return result      

使用生成器的情況:

1 def index_word(text):
2     if text:
3         yield 0
4     for index,letter in enumerate(text,1):
5         if letter == " ":
6             yield index      

這裡,至少有兩個充分的理由說明,使用生成器比不使用生成器代碼更加清晰:

  1、使用生成器以後,代碼行數更少。大家要記住,如果想把代碼寫的Pythonic,在保證代碼可讀性的前提下,代碼行數越少越好。

  2、不使用生成器的時候,對于每次結果,我們首先看到的是result.append(index),其次,才是index。也就是說,我們每次看到的是一個清單的append操作,隻是append的是我們想要的結果。使用生成器的時候,直接yield  index,少了清單append操作的幹擾,我們一眼就能夠看出,代碼是要傳回Index。

這個例子充分說明了,合理使用生成器,能夠有效提高代碼可讀性。隻要大家完全接受了生成器的概念,了解了yield語句和return語句一樣,也是傳回一個值。那麼,就能夠了解為什麼使用生成器比不使用生成器要好,能夠了解使用生成器真的可以讓代碼變得清晰易懂。

6、使用生成器的注意事項

相信通過這篇文章,大家已經能夠了解生成器的作用和好處了。但是,還沒有結束,使用生成器,也有一點注意事項。

我們直接來看例子,假設檔案中儲存了省份的人口總數,現在,需要求每個省份的人口占全國總人口的比例。顯然,我們需要先求出全國的總人口,然後在周遊每個省份的人口,用每個省的人口數除以總人口數,就得到了每個省份的人口占全國人口的比例。

如下所示:

1 def get_province_population(filename):
 2     with open(filename) as f:
 3         for line in f:
 4             yield int(line)
 5 
 6 gen=get_province_population("data.txt")
 7 all_population = sum(gen)
 8 #print all_population
 9 for population in gen:
10     print(population/all population)      

執行上面的這段代碼将不會有任何輸出,這是因為,生成器隻能周遊一次。在我們執行sum語句的時候,就周遊了我們的生成器,當我們再次周遊我們的生成器的時候,将不會有任何記錄。是以,上面的代碼不會有任何輸出。

是以,生成器的唯一注意事項就是:生成器隻能周遊一次。

7、總結

本文深入淺出地介紹了Python中,一個容易被大家忽略的重要特性,即Python的生成器。在實際工作中,充分利用Python生成器,不但能夠減少記憶體使用,還能夠提高代碼可讀性。掌握生成器也是Python高手的标配。希望本文能夠幫助大家了解Python的生成器。

二、疊代器

疊代是通路集合元素的一種方式。疊代器是一個可以記住周遊位置的對象。疊代器隻能往前不能後退。

1、什麼是可疊代對象(Iterable)

  —  集合資料類型,如 list 、tuple、dict、set、str 等

  —  生成器和帶yield 的generator function

2、如何判斷對象可疊代?

  —  from collections import Iterable

  —  isinstance([ ],Iterable)

python中“生成器”、“疊代器”、“閉包”、“裝飾器”的深入了解

              圖2-1 指令行視窗下的操作圖

  如上,指令行模式下,先導入Iterable子產品,然後輸入isinstance([ ],Iterable),括号中前面為待判斷的對象,結果以布爾類型結束(True或False),清單是可疊代對象,是以傳回True。注意:整數是不可疊代對象。

3、什麼是疊代器呢?

疊代器是可以被next() 函數調用并不斷傳回下一個值的對象稱為疊代器。是以生成器是疊代器的子類,但是注意集合類型的可疊代對象不是疊代器。

  —  from collections import Iterator

  —  isinstance((x for x in range(10)),Iterator)

  這兩行代碼是用來判斷是否為疊代器的,傳回True或False。

iter()函數:将可疊代對象轉換成疊代器。

python中“生成器”、“疊代器”、“閉包”、“裝飾器”的深入了解

                圖2-2 疊代器的判斷

三、閉包

1、什麼是閉包?

内部函數對外部函數作用域裡變量的引用(非全局變量),則稱内部函數為閉包。

閉包三要素:

      1、嵌套函數 

      2、變量的引用

      3、傳回内部函數

舉例如下:

1 #定義一個嵌套函數(要素1)
 2 def test(num):
 3     def test_in(num_in):
 4         #内部函數引用外部函數的變量(非全局變量)(要素2)
 5         print("sum = %s"%(num + num_in))
 6         #傳回的結果可以被列印出來
 7         return num,num_in
 8     #傳回内部的函數(要素3)
 9     return test_in
10 #這裡的rtn就是test_in
11 rtn = test(10)
12 print(rtn)
13 #内部函數test_in傳參
14 print(rtn(20))      

輸出結果為:

1 <function test.<locals>.test_in at 0x000000000220E378>
2 sum = 30
3 (10, 20)
4 [Finished in 0.3s]      

定義閉包時,必須滿足上面三個要素。

2、閉包的應用

1 #求一進制一次方程的值,輸入x,求y
2 def fun(a,b):     #其中a,b是固定的
3     def fun_in(x):
4         return a*x+b
5     return fun_in
6 f = fun(3,5)
7 print(f(1))    #每次輸入不同的x值即可求出對應的y值
8 print(f(3))      

輸出結果為:

1 8
2 14
3 [Finished in 0.3s]      

上面的函數中,利用閉包來求一進制一次方程的值,更友善,直接輸入x的值即可求出對應的y的值。因為這利用了閉包可以記住外部函數的參數的特性。

四、裝飾器

1、什麼是裝飾器(decorator)?

裝飾器其實就是一個閉包,把一個函數當作參數然後傳回一個替代版函數。

裝飾器有2個特性:

  1、可以把被裝飾的函數替換成其他函數

  2、可以在加載子產品時候立即執行

舉例如下:

  A、裝飾器對無參數函數進行裝飾:

1 #裝飾器本質就是一個閉包,
 2 #将函數當成參數傳進去
 3 def deco(fun):
 4     def inner():
 5         print("你想進行的操作1")
 6         print("你想進行的操作2")
 7         fun()
 8     return inner
 9 #@deco是一個“文法糖”
10 @deco
11 def test():
12     print("test函數的操作")
13 #這個test已經被裝飾了,不是原來的test
14 test()
15 #14行的test()等同于如下操作:
16 #rtn = deco(test)
17 #rtn()      

輸出結果為:

1 你想進行的操作1
2 你想進行的操作2
3 test函數的操作
4 [Finished in 0.3s]      

2、裝飾器的功能有:

        1、引入日志

        2、函數執行時間統計

        3、執行函數前預備處理

        4、執行函數後清理功能

        5、權限z校驗等場景

        6、緩存

3、裝飾器的分類

裝飾器可分為對有無參數函數進行裝飾的裝飾器和對有無傳回值函數進行裝飾的裝飾器,組合起來一共有4種。即:裝飾器對無參數無傳回值的函數進行裝飾,裝飾器對無參數有傳回值的函數進行裝飾,裝飾器對有參數無傳回值的函數進行裝飾,裝飾器對有參數有傳回值的函數進行裝飾。 

下面舉一個裝飾器實際應用的例子。

  A、裝飾器對無參數函數進行裝飾

1 #定義函數:完成包裹資料
 2 def makeBold(fn):
 3     def wrapped():
 4         return "<b>" + fn() + "</b>"
 5     return wrapped
 6 
 7 #定義函數:完成包裹資料
 8 def makeItalic(fn):
 9     def wrapped():
10         return "<i>" + fn() + "</i>"
11     return wrapped
12 
13 @makeBold
14 def test1():
15     return "hello world - 1"
16 
17 @makeItalic
18 def test2():
19     return "hello world - 2"
20 
21 @makeBold
22 @makeItalic
23 def test3():
24     return "hello world - 3"
25 
26 print(test1())
27 print(test2())
28 print(test3())      

輸出結果為:

1 <b>hello world - 1</b>
2 <i>hello world - 2</i>
3 <b><i>hello world - 3</i></b>
4 [Finished in 0.3s]      

上面這個例子是做網頁的時候對字型進行設定,對test1()進行加粗,對test2()斜體處理,對test3()先斜體在加粗。注意:對一個函數可以同時使用多個裝飾器,裝飾順序由内而外。

  B、裝飾器對有參數函數進行裝飾

1 #定義一個裝飾器
 2 def deco(func):
 3     def wrapper(a,b):    #内部函數的參數必須和被裝飾的函數保持一緻
 4         print("添加的功能")
 5         func(a,b)
 6 
 7     return wrapper
 8 
 9 @deco
10 #有參數的函數
11 def sum(a,b):
12     print(a+b)
13 
14 sum(10,20)      

輸出結果為:

1 添加的功能
2 30
3 [Finished in 0.3s]      

當裝飾器裝飾有參數的函數時,裝飾器内部的函數也必須帶有和其相同的參數,因為被裝飾的參數會被當成參數傳進裝飾器的内部函數中,如果兩者的參數不一緻,會報錯。

C、裝飾器對不定長參數函數進行裝飾

1 from time import ctime,sleep
 2 #定義一個裝飾器,裝飾不定長參數函數
 3 def deco(func):
 4     def wrapper(*args,**kwargs):
 5         print("%s called at the %s"%(func.__name__,ctime()))
 6         func(*args,**kwargs)
 7     return wrapper
 8 
 9 @deco
10 def test1(a,b,c):
11     print(a+b+c)
12 
13 @deco
14 def test2(a,b):
15     print(a+b)
16 
17 test1(10,20,30)
18 sleep(2)
19 test1(30,40,50)
20 
21 sleep(1)
22 test2(10,20)      

輸出結果為:

1 test1 called at the Fri Nov 10 19:08:03 2017
2 60
3 test1 called at the Fri Nov 10 19:08:05 2017
4 120
5 test2 called at the Fri Nov 10 19:08:06 2017
6 30
7 [Finished in 3.3s]      

 D、裝飾器對有傳回值的函數進行裝飾

1 from time import ctime,sleep
 2 #定義一個裝飾器,裝飾不定長參數函數
 3 def deco(func):
 4     def wrapper(*args,**kwargs):
 5         print("%s called at the %s"%(func.__name__,ctime()))
 6         return func(*args,**kwargs)
 7     return wrapper
 8 #上面的裝飾器是一個萬能的裝飾器,因為它可以裝飾任意一種函數
 9 #包括有無參數函數和有無傳回值函數
10 
11 @deco
12 def test():
13     return "---ha--ha---"
14 
15 t = test()
16 print(t)      

輸出結果為:

1 test called at the Fri Nov 10 19:19:20 2017
2 ---ha--ha---
3 [Finished in 0.3s]      

上面的裝飾器是一個萬能的裝飾器,因為其可以用來裝飾任何函數,包括有無參數函數和有無傳回值函數。

轉載于:https://www.cnblogs.com/tianyiliang/p/7811041.html