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