詳談Python高階函數與函數裝飾器(推薦)
一、上節(jié)回顧
Python2與Python3字符編碼問題,不管你是初學者還是已經對Python的項目了如指掌了,都會犯一些編碼上面的錯誤。我在這里簡單歸納Python3和Python2各自的區(qū)別。
首先是Python3-->代碼文件都是用utf-8來解釋的。將代碼和文件讀到內存中就變成了Unicode,這也就是為什么Python只有encode沒有decode了,因為內存中都將字符編碼變成了Unicode,而Unicode是萬國碼,可以“翻譯”所以格式編碼的格式。Python3中str和bytes是兩種格式,bytes可以當做二進制的表現(xiàn)形式。
Python2使用系統(tǒng)默認的字符編碼解釋代碼,所以要用utf-8解釋代碼,就必須在頭部申明;并且Python2中有解碼和編碼,但是解碼動作是必須的而編碼動作可以忽略,因為Python代碼加載到內存中就是Unicode,這一點和python3一樣;Python2中還需要注意的就是str和bytes是一個意思。Python2 里面的str就是Python3中的bytes格式,而Python3中的str其實就是Unicode.
函數基礎(這里我就是用遞歸函數中的二分查找)
為什么使用函數:將將程序進行模塊設計
定義函數有三種形式:
- 無參函數
- 有參函數
- 空函數
PS:如果函數有多個返回值,那么返回的來的數據格式是元組
- 如何在函數傳入參數時限定參數數據格式。
def leon(x:int,y:int)->int:
pass
其中這里指定了x,y都必須是int類型 " -> "的意思是函數返回值也必須是int類型
print(yan.__annotations__):顯示形參的限定數據格式以及返回值的格式
a = [1,2,3,4,5,7,9,10,11,12,14,15,16,17,19,21] #形參中的num def calc(num,find_num): print(num) mid = int(len(num) / 2) #中間數的下標 if mid == 0: #遞歸函數非常重要的判斷條件 if num[mid] == find_num: print("find it %s"%find_num) else: print("cannt find num") if num[mid] == find_num: #直接找到不用遞歸,結束函數 print("find_num %s"%find_num) elif num[mid] > find_num: #find_num應該在左邊,向下遞歸 calc(num[0:mid],find_num) elif num[mid] < find_num: #find_num應該在右邊,向下遞歸 calc(num[mid+1:],find_num) calc(a,12)
匿名函數
c = lambda x:x+1 #x就是形參,c就是這個匿名函數的對象 print(c(22))
高階函數-特性
1. 把一個函數的內存地址傳給另外一個函數,當做參數
2.一個函數把另外一個函數的當做返回值返回
def calc(a,b,c): print(c(a) + c(b)) calc(-5,10,abs) #引用上一節(jié)的實例,將-5和10絕對值相加
二、高階函數(補充)
函數是第一類對象
函數可以被賦值
可以被當做參數
可以當做返回值
可以作為容器類型的元素
#函數可以被賦值 def leon(): print("in the leon") l = leon l() #函數可以被當做參數 def yan(x): #這里x形參,其實就是我們調用實參的函數名 x() #運行函數 y = yan(leon) #函數當做返回值 def jian(x): 和上面一樣這這也必須傳入一個函數 return x j = jian(leon) #這里需要注意一點就是這里的意思是運行jian這個函數而這個函數返回的是x 也就是leon這個函數的內存地址,也就是說這時候leon這個函數并沒有被執(zhí)行 j() #運行 leon函數 #可以做為容器類型的元素 leon_dict = {"leon":leon} leon_dict["leon"]() #這樣也可以運行l(wèi)eon這個函數
三、閉包函數
1.什么是閉包?我來看一下,比較官網的概念(這不是我在官網上面找的,不過沒有關系,反正你們也看不懂):
閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即使已經離開了創(chuàng)造它的環(huán)境也不例外。所以,閉包是由函數和與其相關的引用環(huán)境組合而成的實體。
懵逼了?不存在的。下面我用簡潔的說一下,但是有一點很重要,閉包是裝飾器中的重點,如果沒有把閉包正真理解,那么學完裝飾器之后會很快忘記。我們通過一個列子來說明下
import requests #首先導入一個模塊,這個可以不用記 def get(url): #定義一個get函數里面需要傳一個url的位置參數 def wapper(): #在定義一個wapper函數 res = requests.get(url) #這一步就是打開一個網頁 return res.text #將網頁以文字的形式返回 return wapper #返回最里層的wapper函數 g = get("http://www.baidu.com") #調用:首先因為作用域的原因,我們無法訪問到里層的wapper函數,所以我們直接調用get函數這里返回了一個wapper函數 print(g()) # 然后我在調用g(get函數)的對象,這樣是不是就訪問到里層的wapper函數呢
PS:這里我們可以把函數當做一個特殊的變量,當代碼從上向下執(zhí)行的時候,如果函數不被調用話,函數內的代碼是不會被執(zhí)行的。就拿上面的上面的舉例,當我們執(zhí)行get函數的時候,這時候會返回一個wapper函數的內存地址,但是這個時候wapper函數并沒有被執(zhí)行也就是說g()這時候返回的狀態(tài)其實就是wapper,這是我們只需要將g運行,就等于運行了wapper內的代碼。
四、函數的嵌套調用
嵌套調用其實很好理解,就是在一個函數中調用另一個函數的結果,也就是return的東西,同樣的我們看一段非常簡單的代碼來看一下。
#嵌套調用,在一個函數中調用另一個函數的功能 #calc這個函數就是在對比兩個數字的大小 def calc2(x,y): if x >y : return x else: return y #我靠老板非常變態(tài),然你直接計算四個數字的大小,擦。 def calc4(a,b,c,d): res1 = calc2(a,b) #res1的值,這里不就是calc2這個函數比較時最大的哪一個嗎。 res2 = calc2(res1,c) res3 = calc2(res2,d) return res3
通過上面的代碼我們做一記憶。什么時候會用到嵌套調用呢?很顯然,就是我們這個函數(calc4)需要另外一個函數的實行結果(return的y或者x)。
五、裝飾器(高級的閉包函數)
就拿下面的這段代碼來說。如何在不改源代碼的情況下實現(xiàn)計算代碼的運行時間
def geturl(url): response = requests.get(url) print(response.status_code) geturl(http://www.baidu.com)
def timer(func): def wapper(url): start_time = time.time() func(url) stop_time = time.time() so_time_is = stop_time - start_time print("運行時間%s"%so_time_is) return wapper @timer def geturl(url): response = requests.get(url) print(response.status_code) python = geturl(http://www.baidu.com)
圖解代碼
裝飾器必備:
@timer就是裝飾器,意思是裝飾它下面的函數,而裝飾器和被裝飾的都是一個函數。
timer(裝飾器函數),首先它會有一個位置參數(func)名字隨意,但是必須并且只能是一個位置參數
func參數就是被裝飾的geturl這個函數
為什么func是geturl這個函數呢-->上面寫了一個裝飾器功能:geturl=timer(geturl),我們看到這里的timer中傳入的其實就是func函數所以func = geturl(被裝飾的函數)
分析geturl=timer(geturl),首先我們可以得知timer這是一個閉包函數,當我們執(zhí)行這個閉包函數,會把里層的函數(wapper)返回,也就是說timer(geturl)其實就是返回的wapper,所以就可以這樣理解了geturl==wapper,所以當我們運行geturl的時候就相當于在執(zhí)行wapper()這樣的一個操作;如果這里實在記不住,就這樣。咱上面不是有一個閉包函數嗎?你就把geturl=timer(geturl)中的geturl(執(zhí)行函數的返回結果)當做上面g(函數調用的返回結果),然后在分別再執(zhí)行了下"g"或者"geturl”這個對象。
如果被裝飾者有位置參數的話,我們需要在wapper函數中加上對應的位置參數用來接收,如果長度是不固定的話還可以用*args和**kwargs
六、有參裝飾器
聽著名字顧名思義,就是在裝飾器中還有位置參數。
#一個low得不能再low得驗證腳本,如果是顯示環(huán)境中所有數據必須是由數據庫或者一個靜態(tài)文件提供,并且登錄成功時,需要保存用戶的一個狀態(tài) def auth(auth_type): #有參裝飾器名稱 def auth_deco(func): #定義第二層函數名稱 def wrapper(*args,**kwargs): #最里層函數,主要實現(xiàn)認證功能 if auth_type == "file": username = input("username>>:").strip() password = input("username>>").strip() if username == "leon" and password == "loveleon": res = func(*args,**kwargs) return res elif auth_type == "mysql_auth": print("mysql_auth...") return func(*args,**kwargs) return wrapper #第二層返回的是wrapper函數,其實就是home return auth_deco #第一層返回的結果等于第二層函數的名稱 @auth('file') def home(): print("welcome") home() #執(zhí)行home-->wrapper
有參函數必備知識:
套路,通過上面無參裝飾器,我們得出了geturl=timer(geturl)這個等式?;氐接袇⒀b飾器,我們又會有什么樣子的等式呢?首先@auth("file")是一個裝飾器也就是一個函數,所以我們定義了一個auth(auth_type)這個函數,而這個函數返回的是什么呢?沒有錯就是第二層函數;到了這里我們就會發(fā)現(xiàn)@auth("file")其實就是@auth_deco,現(xiàn)在我們知道了現(xiàn)在裝飾器其實就是auth_deco,那剩下的還不知道怎么寫嗎?
整理公式,auth('file')-----------(return)> auth_deco----->@auth_deco ->home=auth_deco(home)
如果記不住?如果實在是記不住,其實就可以這樣理解,有參裝飾器無非就是在無參裝飾器上面加了一層(三層),然后在第一層返回了第二層的函數,而到了第二層就和我們普通用的裝飾器是一毛一樣了
七、模塊導入
import ,創(chuàng)建一個leonyan.py的模塊文件,等待被導入
a = 10 b = 20 c = 30 def read1(): print("in the read1") def read2(): print("in the read2")
導入leonyan.py文件(調用模塊文件和模塊文件在同一目錄下)
import leonyan #Python IDE這行會爆紅,但是不用管 leonyan.read1() #執(zhí)行l(wèi)eonyan這個包中的read1函數 leonyan.read2() #執(zhí)行l(wèi)eonyan這個包中read2函數 print(leonyan.a + leonyan.b + leonyan.c ) #輸出60
總結:在Python中包的導入(import ***)會干三個事情:1:創(chuàng)建新的作用域;2:執(zhí)行該作用域的頂級代碼,比如你導入的那個包中有print執(zhí)行后就會直接在屏幕中輸出print的內容;3:得到一個模塊名,綁定到該模塊內的代碼
在模塊導入的時候給模塊起別名
import leonyan as ly import pandas as pd #這是一個第三方模塊,以后的博客中會寫到,這是一個用于做統(tǒng)計的
給模塊起別名還是挺多的,在有些模塊的官方文檔中,還是比較推薦這種方法的,比如pandas的官方文檔中就是起了一個pd別名,總之as就是一個模塊起別名
from *** import *** from leonyan import read1 #引入直接調用 read1()
如果在調用模塊的函數作用域中有相同的同名的,會將調用過來的覆蓋。
在form ** import ** 中控制需要引用的變量(函數其實在未被執(zhí)行的時候也是一個存放在內存中的變量)
from leonyan import read1,read2 在同一行中可以引用多個,只需要用逗號隔開就行了 print(read1) print(read2) #這里打印的就是read1和read2的內存地址 #需求我現(xiàn)在只需要導入read2 這時候我們就可以在leonyan這個函數中加上這么一行: __all__ = ["read2"] #這里的意思就是別的文件調用為的時候用from ** import ** 只能拿到read2 這個函數的內存地址,也就是只有read2可以被調用
把模塊當做一個腳本執(zhí)行
我們可以通過模塊的全局變量__name__來查看模塊名:
當做腳本運行:
__name__ 等于'__main__'
作用:用來控制.py文件在不同的應用場景下執(zhí)行不同的邏輯
if __name__ == '__main__':
#fib.py def fib(n): # write Fibonacci series up to n a, b = 0, 1 while b < n: print(b, end=' ') a, b = b, a+b print() def fib2(n): # return Fibonacci series up to n result = [] a, b = 0, 1 while b < n: result.append(b) a, b = b, a+b return result if __name__ == "__main__": import sys fib(int(sys.argv[1]))
代碼執(zhí)行 Python flb.py 100
只需要簡單了解的Python模塊導入搜索路徑
內建(build-in) --> sys.path(sys.path是一個列表,而且第一個位置就是當前文件夾)
模塊導入的重點-->包的導入
在實際的開發(fā)環(huán)境中,你不可能一個文件的代碼寫到底,當然你也有可能會引用同文件夾中的其他模塊,但是你有沒有想過這一個項目不可能是你一個寫的,都是很多人協(xié)作開發(fā)。這樣就存在這樣的一個問題了;不同的人不可能用一臺電腦,也不可能在一個文件夾下面寫寫功能。他們也有自己的代碼文件夾,然后大家把功能通過接口的方式,提供調用。這時候就面臨這不同文件夾的調用問題。而這種問題也需要通過from ** import ** 調用。
上圖中我運行“模塊導入.py”這個文件夾,首先我from Pythonscript.leonyan.command import config,因為我們得運行腳本和需要導入的包都在Pythonscript的目錄下面所以我直接通過絕對路徑導入,然后一層一層“.”下去,知道最后import了這個config文件,這里我們需要注意一點:當腳本在最外層運行的時候sys.path 列表中的第一個參數就是運行腳本的目錄,這是什么意思,這代表著你在別的包中有調用了其他的東西比如說我的config.py是調用了bing.py文件,這時候就必須寫絕對路徑,因為這在sys.path文件夾中已經找不到了,也就是導入不進來。
總結:包的導入其實都是很簡單的,你需要記住一點:當你導入Python內建或者下載的第三方模塊直接用import 導入,如果是自己寫的就用from ** import ** 使用絕對目錄導入就行了,也就是從調用腳本的上級目錄開始導入。這樣可以保證不會報模塊導入的錯誤了。
以上這篇詳談Python高階函數與函數裝飾器(推薦)就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Python使用Redis實現(xiàn)作業(yè)調度系統(tǒng)(超簡單)
Redis作為內存數據庫的一個典型代表,已經在很多應用場景中被使用,這里僅就Redis的pub/sub功能來說說怎樣通過此功能來實現(xiàn)一個簡單的作業(yè)調度系統(tǒng)。這里只是想展現(xiàn)一個簡單的想法,所以還是有很多需要考慮的東西沒有包括在這個例子中,比如錯誤處理,持久化等2016-03-03使用matplotlib實現(xiàn)在同一個窗口繪制多個圖形
這篇文章主要介紹了使用matplotlib實現(xiàn)在同一個窗口繪制多個圖形問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-08-08pytorch實現(xiàn)mnist數據集的圖像可視化及保存
今天小編就為大家分享一篇pytorch實現(xiàn)mnist數據集的圖像可視化及保存,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-01-01