Python核心編程的四大神獸

来源:https://www.cnblogs.com/ssy3340/archive/2018/10/06/9747722.html
-Advertisement-
Play Games

本文將主要分為4大部分,分別介紹Python核心編程中的迭代器、生成器 、閉包以及裝飾器。 生成器 生成器是生成一個值的特殊函數,它具有這樣的特點:第一次執行該函數時,先從頭按順序執行,在碰到yield關鍵字時該函數會暫停執行該函數後續的代碼,並且返回一個值;在下一次調用該函數執行時,程式將從上一次 ...


本文將主要分為4大部分,分別介紹Python核心編程中的迭代器、生成器 、閉包以及裝飾器。

生成器

生成器是生成一個值的特殊函數,它具有這樣的特點:第一次執行該函數時,先從頭按順序執行,在碰到yield關鍵字時該函數會暫停執行該函數後續的代碼,並且返回一個值;在下一次調用該函數執行時,程式將從上一次暫停的位置繼續往下執行。

通過一個例子來理解生成器的執行過程。求1-10的所有整數的立方並將結果列印輸出,正常使用列表的實現如下:

 

def lifang_ls():
 """求1-10所用整數的立方數-列表方式實現"""
 ls = []
 for i in range(1,11):
   result = i ** 3
   ls.append(result)
 print(ls)

if __name__ == '__main__':
 lifang_ls()

 

輸出結果如下:

當數據量很少時,可以很快得到結果。但是如果範圍擴大到10000甚至是100000000,就會發現程式執行時間會變長,變卡,甚至有可能會因超出記憶體空間而出現程式崩潰的現象。這是因為當數據量變得非常大的時候,記憶體需要開闢很大的空間去存儲這些數據,記憶體都被吃了,自然會變慢變卡。使用生成器就能解決這個問題。

對於上述同一個問題用生成器實現如下,將範圍擴大到1-10000000:

 

def lifang_generate():
 """求1-10000000所用整數的立方數-生成器方式實現"""
 for i in range(1,10000001):
   result = i ** 3
   yield result

if __name__ == '__main__':
 G = lifang_generate()

  

執行效果如下:

可以看到沒有任何的結果輸出,這說明程式已經可以順利執行。對於迭代器來講需要用next()方法來獲取值,修改主函數為以下情況可以列印輸出前4個整數的立方數:

 

if __name__ == '__main__':
 G = lifang_generate()
 print(next(G))
 print(next(G))
 print(next(G))
 print(next(G))

  

輸出結果如下:

 

 

 

到此可以看到,生成器生成的值需要使用next()方法一個一個的取,它不會一次性生成所有的計算結果,只有在取值時才調用,這時程式會返回計算的一個值且程式暫停;下一次取值時從上一次中斷了的地方繼續往下執行。

以取出前3個值為例,下圖為生成器代碼解析圖:

 

 

圖解:Python解釋器從上往下解釋代碼,首先是函數定義,這時在電腦記憶體開闢了一片空間來存儲這個函數,函數沒有被執行,繼續往下解釋;到了主函數部分,首先執行藍色箭頭1,接著往下執行到藍色箭頭2第一次調用生成器取值,此時生成器函數lifang_generate()開始執行,執行到生成器函數lifang_generate()的藍色箭頭2碰到yield關鍵字,這時候生成器函數暫停往下執行並且將result的結果返回,由於是第一次執行,因此result存儲著1的立方的值,此時將1返回,第54行代碼print(first)將結果列印輸出。

主函數中程式接著往下執行到藍色箭頭3,生成器函數lifang_generate()第二次被調用,與第一次不同,第二次從上一次(也就是第一次)暫停的位置繼續往下執行,上一次停在了yield處,因此藍色箭頭3所作的事情就是執行yield後面的語句,也就是第48行print('end'),執行完成之後因for迴圈條件滿足,程式像第一次執行那樣,執行到yield處暫停並返回一個值,此時返回的是2的立方數,在第57行列印輸出8。

第三次調用(藍色箭頭4)與第二次類似,在理清了執行過程之後,程式執行結果如下:

 

迭代器

這裡先拋出兩個概念:可迭代對象、迭代器。

凡是可以通過for迴圈遍歷其中的元素的對象,都是可迭代對象;之前學習得組合數據類型list(列表)、tuple(元組)、dict(字典)、集合(set)等,上一小節介紹得生成器也可以使用for迴圈來遍歷,因此,生成器也是迭代器,但迭代器不一定就是生成器,例如組合數據類型。

凡是可以通過next訪問取值得對象均為迭代器,生成器就是一種迭代器。可以看到,生成器不僅可以用for迴圈來獲取值,還可以通過next()來獲取。

Python中有一個庫collections,通過該庫的Iterable方法來判斷一個對象是否是可迭代對象;如果返回值為True則說明該對象為可迭代的,返回值為False則說明該對象為不可迭代。用Iterator方法來判斷一個對象是否是迭代器,根據返回值來判斷是否為迭代器。

使用Iterable分別判斷列表,字典,字元串以及一個整數類型是否是可迭代對象的代碼如下:

 

from collections import Iterable

def isiterable():
 """分別判斷列表,字典,字元串100,整形100是不是可迭代對象"""

 ls = isinstance([],Iterable)
 dic = isinstance({},Iterable)
 strs = isinstance('100',Iterable)
 ints = isinstance(100,Iterable)
 print('輸出True表示可迭代,False表示不可迭代\n\
 ls為{},dic為{},strs為{},ints為{}'.format(ls,dic,strs,ints))

def main():
 isiterable()
 
if __name__ == '__main__':
 main()

  

 

執行的輸出結果如下:

 

使用Iterator判斷一個對象是否是迭代器的代碼如下,與判斷是否為可迭代對象類似:

 

from collections import Iterable,Iterator


def print_num():
 """定義一個產生斐波那契數列的生成器"""
 a,b = 0,1
 for i in range(1,10):
   yield b
   a,b = b,a + b
 
def isiterator():
 """分別判斷列表,字典、生成器是否為迭代器"""

 ls_ret = isinstance([],Iterator)
 dict_ret = isinstance({},Iterator)
 genarate_ret = isinstance((x * 2 for i in range(10)),Iterator)
 print_num_ret = isinstance(print_num(),Iterator)
 print('輸出True表示該對象為迭代器,False表示該對象不是迭代器\n\
   ls輸出為{},dict輸出為{},genarate輸出為{},print_num輸出為{}'.format(ls_ret,dict_ret,genarate_ret,print_num_ret))

def main():
 isiterator()

if __name__ == '__main__':
 main()

  

輸出的結果如下:

 

組合數據類型不是迭代器,但是屬於可迭代對象,可以通過iter()函數將其轉換位迭代器,這樣就可以使用next方法來獲取對象各個元素的值,代碼如下:

 

from collections import Iterable,Iterator

def trans_to_iterator():
 """使用iter()將可迭代類型-列表轉換為迭代器"""

 ls = [2,4,6,8,10]
 ls_ierator = iter(ls)
 ls_ierator_is = isinstance(ls_ierator,Iterator)
 print('轉換後的返回值為{},使用next取出的第一個元素的值為{}'.format(ls_ierator_is,next(ls_ierator)))
def main():
 trans_to_iterator()

if __name__ == '__main__':
 main()

  

 

輸出結果為:

 

 

 

閉包

內部函數對外部函數變數的引用,則將該函數與用到的變數稱為閉包。以下為閉包的例子:

 

def func(num):

print('start')
def func_in():
"""閉包內容"""
  new_num = num ** 3
  print(new_num)
return func_in

if __name__ == '__main__':
ret = func(10)
ret()

  

 

理解閉包是理解裝飾器的前提,同樣通過一張圖來理解閉包的執行過程:

 

 


圖解:Python解釋器從上往下解釋代碼,首先定義一個函數,func指向了該函數(紅箭頭所示);接著到主函數執行第14行代碼 ret = func(10),此時先執行賦值號“=”右邊的內容,這裡調用了函數func()並傳入10這個實參,函數func()代碼開始執行,先是列印輸出“start”,接著定義了一個函數func_in(),func_in指向了該函數,函數沒有被調用,程式接著往下執行,return func_in 將函數的引用返回,第14行代碼用ret接收了這個返回值,到此ret就指向了func_in所指向的函數體(綠箭頭所示)。最後執行ret所指的函數。這就是閉包的整個過程,func_in()函數以及該函數內用到的變數num就稱為閉包。

裝飾器

代碼的編寫需要遵循封閉開放原則,封閉是指對於已有的功能代碼實現不允許隨意進行修改,開放是指能夠對已有的功能進行擴展。例如一款手游,現在已經能夠實現現有的游戲模式,但隨著外部環境的變化發展(市場競爭,用戶體驗等),現有的游戲模式已經不能滿足用戶的需求了。為了留住用戶,需要加入更多的玩法來保持用戶對該款游戲的新鮮感,於是開發方在原來游戲的基礎上又開發了好幾種游戲模式。像這樣,新的游戲版本既增加了先的游戲模式,又保留了原有的游戲模式,體現了封閉開放的原則。 裝飾器的作用就是在不改變原來代碼的基礎上,在原來的功能上進行拓展,保證開發的效率以及代碼的穩定性。 列印輸出九九乘法表可以通過以下代碼實現:

 

def func_1():
"""列印輸出九九乘法表"""
for i in range(1,10):
  for j in range(1,i + 1):
    result = i * j
    print('{}*{}={}'.format(i,j,result),end=' ')
  print('')
if __name__ == '__main__':
func_1()

  

 

輸出結果如下: 假如現在需要實現一個功能,在不修改func_1函數代碼的前提下,在九九乘法表前增加一個表頭說明,在乘法表最後也增加一個說明。下麵的代碼實現了裝飾器的功能:

 

def shuoming(func):
def shuoming_in():
  print('以下為九九乘法表:')
  func()
  print('以上為九九乘法表')
return shuoming_in

def func_1():
"""列印輸出九九乘法表"""
for i in range(1,10):
  for j in range(1,i + 1):
    result = i * j
    print('{}*{}={}'.format(i,j,result),end=' ')
  print('')

if __name__ == '__main__':
func_1 = shuoming(func_1)
func_1()

  

 

輸出結果如下: 可以看到func_1函數的代碼沒有任何修改,還實現了問題提出的要求,這其中的核心就在於最後兩行代碼。通過下圖來理解裝飾器執行的過程:

 

圖解:跟之前一樣,Python解釋器自上往下解釋代碼,遇到定義函數的代碼不用管,因為沒有調用函數是不會執行的;這樣直接就來到了第22行代碼中,程式先執行賦值號“=”右邊的代碼,shuoming(func_1)調用了之前定義的函數,並傳入了func_1實參,程式轉到shuoming(func)執行,形參func接收實參func_1,此時func也指向了func_1所指向的函數(如圖中分界線上方白色方框內的藍箭頭所示);在shuoming()函數中代碼繼續往下走,在shuoming()函數內容又定義了一個shuoming_in()函數(如圖中分界線上方白色方框內的藍色方框所示),接著往下,將shuoming_in()函數的引用返回,至此shuoming()函數執行完畢,程式回到第22行代碼執行,shuoming()函數的返回值被func_1接收,此時,func_1不在指向原來的函數,轉成指向shuoming_in所指向的函數(如圖中分界線下方白色方框內的黃色箭頭)。最後調用func_1所指向的函數,也就是shuoming_in()函數,shuoming_in()函數內的func指向了原來func_1()所指的函數(也就是生成九九乘法表的函數),因此程式最終的結果就在九九乘法表前後各加了一個說明性字元串。

以上為裝飾器的執行過程,但是以上裝飾寫法不夠簡潔,大多數情況下採取以下寫法,輸出結果是一樣的:

 

def shuoming(func):
 def shuoming_in():
   print('以下為九九乘法表:')
   func()
   print('以上為九九乘法表')
 return shuoming_in

"""@shuoming相當於func_1 = shuoming(fucn_1)"""
@shuoming
def func_1():
 """列印輸出九九乘法表"""
 for i in range(1,10):
   for j in range(1,i + 1):
     result = i * j
     print('{}*{}={}'.format(i,j,result),end=' ')
   print('')
if __name__ == '__main__':
 """直接調用func_1即可完成裝飾"""
 func_1()

  

 

有時候有些被裝飾的函數可能有以下幾種情況:存在或不存在參數,有返回值或沒有返回值,參數可能定長或不定長等等,為了通用性,與爬蟲的請求代碼一樣,裝飾器有著通用的寫法:

 

def tongyong(func):
def tongyong_in(*args,**kwargs):
  ret = func(*args,**kwargs)
  return ret
return tongyong_in

  

 

使用這個裝飾器裝飾九九乘法表一樣可以正常輸出,如果需要特定的裝飾效果,修改這個通用代碼即可。 

結束

以上為生成器、迭代器、閉包以及裝飾器的所有內容,其中裝飾器屬於難點。理解裝飾器的執行過程能夠更好的幫助我們進階學習Python。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 鏈接:https://www.nowcoder.com/acm/contest/206/B來源:牛客網 題目描述 恬恬有一個nx n的數組。她在用這個數組玩游戲: 開始時,數組中每一個元素都是0。 恬恬會做某些操作。在一次操作中,她可以將某一行的所有元素同時加上一個值,也可以將某一列的所有元素同時加 ...
  • 我先說說feed流吧,它就是社交網站中用戶活動信息流,例如用戶寫了博客、發了照片、評論了什麼等等。Facebook叫newsFeed、推特叫TimeLineFeed。ActivityStream是這些feed規範,它有演員、動作、對象、目標等重要元素組成。用ActivityStream作為信息模型具 ...
  • if語句 * if語句有三種格式 * * if語句格式1: * if(關係表達式){ * 語句體; * } * * 執行順序: * A:首先計算關係表達式的值,看是true還是false * B: 如果是true,執行語句體, * C:如果是false,就不做執行語句體 * * 不影響其它語句的執行 ...
  • 1003 我要通過! (20 分) “ 答案正確 ”是自動判題系統給出的最令人歡喜的回覆。本題屬於 PAT 的“ 答案正確 ”大派送 —— 只要讀入的字元串滿足下列條件, 系統就輸出“ 答案正確 ”,否則輸出“ 答案錯誤”。 得到“ 答案正確 ”的條件是: 字元串中必須僅有 P 、 A 、 T 這三 ...
  • 前面提及過,音頻指紋演算法的思路。 也梳理開源了兩個比較經典的演算法。 https://github.com/cpuimage/shazam https://github.com/cpuimage/AudioFingerprinter 後來一段時間,稍微看了下這兩個演算法,還有不少可以精簡優化的空間。 例 ...
  • 前言 字元串基礎(String) python中字元的定義使用單引號或者雙引號都可以,例如: 註意:在python3中input獲取鍵盤輸入的數據,都以字元串的方式進行保存,即使輸入的是數字。 下標&切片 1.下標 下標:可以理解為數組類數據類型內元素的索引。列表與元組支持下標索引,字元串是字元的數 ...
  • yield表達式用於generator function 調用generator function時,返回一個iterator(函數內語句不被會執行),調用iterator函數時,執行到yield表達式, 當前函數暫停執行,返回表達式的值到調用者,繼續調用iterator函數,從暫停處恢復執行。、 ...
  • 筆者近期在工作之中編程實現一個Cache結構的封裝,需要使用到C++之中的 互斥量Mutex ,於是花了一些時間進行了調研。( 結果對C++標準庫很是絕望.... )最終還是通過利用了Boost庫的 shared_mutex 解決了問題。借這個機會來聊聊在C++之中的多線程編程的一些 “坑” 。 1 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...