下面小編就為大家帶來一篇詳談Python高階函數與函數裝飾器(推薦)。小編覺得蠻不錯的,現在就分享給大家,也給大家做個參考。一起跟著小編過來看看吧
一、上節回顧
Python2與Python3字元編碼問題,不管你是初學者還是已經對Python的專案瞭如指掌了,都會犯一些編碼上面的錯誤。我在這裡簡單歸納Python3和Python2各自的差別。
首先是Python3-->程式碼檔案都是用utf-8來解釋的。將程式碼和檔案讀到記憶體就變成了Unicode,這也就是為什麼Python只有encode沒有decode了,因為記憶體中都將字元編碼變成了Unicode,而Unicode是萬國碼,可以「翻譯」所以格式編碼的格式。 Python3中str和bytes是兩種格式,bytes可以當作二元的表現形式。
Python2使用系統預設的字元編碼解釋代碼,所以要用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) #引用上一节的实例,将-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这个函数并没有被执行 j() #运行 leon函数 #可以做为容器类型的元素 leon_dict = {"leon":leon} leon_dict["leon"]() #这样也可以运行leon这个函数
三、閉包函數
1.什麼是閉包?我來看一下,比較官網的概念(這不是我在官網上面找的,不過沒有關係,反正你們也看不懂):
閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變數的函數。這個被引用的自由變數將會和這個函數一同存在,即使已經離開了創造它的環境也不例外。所以,閉包是由函數和與其相關的引用環境組合而成的實體。
懵逼了?不存在的。下面我用簡潔的說一下,但是有一點很重要,閉包是裝飾器中的重點,如果沒有把閉包正真理解,那麼學完裝飾器之後會很快忘記。我們透過一個列子來說明下
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:這裡我們可以把函數當成一個特殊的變量,當程式碼從上向下執行的時候,如果函數不被呼叫話,函數內的程式碼是不會被執行的。就拿上面的上面的舉例,當我們執行get函數的時候,這時候會回傳一個wapper函數的記憶體位址,但這個時候wapper函式並沒有被執行也就是說g()這時候回傳的狀態其實就是wapper ,這是我們只要將g運行,就等於運行了wapper內的程式碼。
四、函數的巢狀呼叫
巢狀呼叫其實很好理解,就是在一個函數中呼叫另一個函數的結果,也就是return的東西,同樣的我們看一段非常簡單的程式碼來看一下。
#嵌套调用,在一个函数中调用另一个函数的功能 #calc这个函数就是在对比两个数字的大小 def calc2(x,y): if x >y : return x else: return y #我靠老板非常变态,然你直接计算四个数字的大小,擦。 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)。
五、裝飾器(進階的閉包函數)
#就拿下面的這段程式碼來說。如何在不改變原始程式碼的情況下實作計算程式碼的運行時間
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这是一个闭包函数,当我们执行这个闭包函数,会把里层的函数(wapper)返回,也就是说timer(geturl)其实就是返回的wapper,所以就可以这样理解了geturl==wapper,所以当我们运行geturl的时候就相当于在执行wapper()这样的一个操作;如果这里实在记不住,就这样。咱上面不是有一个闭包函数吗?你就把geturl=timer(geturl)中的geturl(执行函数的返回结果)当做上面g(函数调用的返回结果),然后在分别再执行了下"g"或者"geturl”这个对象。
如果被装饰者有位置参数的话,我们需要在wapper函数中加上对应的位置参数用来接收,如果长度是不固定的话还可以用*args和**kwargs
六、有参装饰器
听着名字顾名思义,就是在装饰器中还有位置参数。
#一个low得不能再low得验证脚本,如果是显示环境中所有数据必须是由数据库或者一个静态文件提供,并且登录成功时,需要保存用户的一个状态 def auth(auth_type): #有参装饰器名称 def auth_deco(func): #定义第二层函数名称 def wrapper(*args,**kwargs): #最里层函数,主要实现认证功能 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() #执行home-->wrapper
有参函数必备知识:
套路,通过上面无参装饰器,我们得出了geturl=timer(geturl)这个等式。回到有参装饰器,我们又会有什么样子的等式呢?首先@auth("file")是一个装饰器也就是一个函数,所以我们定义了一个auth(auth_type)这个函数,而这个函数返回的是什么呢?没有错就是第二层函数;到了这里我们就会发现@auth("file")其实就是@auth_deco,现在我们知道了现在装饰器其实就是auth_deco,那剩下的还不知道怎么写吗?
整理公式,auth('file')-----------(return)> auth_deco----->@auth_deco ->home=auth_deco(home)
如果记不住?如果实在是记不住,其实就可以这样理解,有参装饰器无非就是在无参装饰器上面加了一层(三层),然后在第一层返回了第二层的函数,而到了第二层就和我们普通用的装饰器是一毛一样了
七、模块导入
import ,创建一个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() #执行leonyan这个包中的read1函数 leonyan.read2() #执行leonyan这个包中read2函数 print(leonyan.a + leonyan.b + leonyan.c ) #输出60
总结:在Python中包的导入(import ***)会干三个事情:1:创建新的作用域;2:执行该作用域的顶级代码,比如你导入的那个包中有print执行后就会直接在屏幕中输出print的内容;3:得到一个模块名,绑定到该模块内的代码
在模块导入的时候给模块起别名
import leonyan as ly import pandas as pd #这是一个第三方模块,以后的博客中会写到,这是一个用于做统计的
给模块起别名还是挺多的,在有些模块的官方文档中,还是比较推荐这种方法的,比如pandas的官方文档中就是起了一个pd别名,总之as就是一个模块起别名
from *** import *** from leonyan import read1 #引入直接调用 read1()
如果在调用模块的函数作用域中有相同的同名的,会将调用过来的覆盖。
在form ** import ** 中控制需要引用的变量(函数其实在未被执行的时候也是一个存放在内存中的变量)
from leonyan import read1,read2 在同一行中可以引用多个,只需要用逗号隔开就行了 print(read1) print(read2) #这里打印的就是read1和read2的内存地址 #需求我现在只需要导入read2 这时候我们就可以在leonyan这个函数中加上这么一行: __all__ = ["read2"] #这里的意思就是别的文件调用为的时候用from ** import ** 只能拿到read2 这个函数的内存地址,也就是只有read2可以被调用
把模块当做一个脚本执行
我们可以通过模块的全局变量__name__来查看模块名:
当做脚本运行:
__name__ 等于'__main__'
作用:用来控制.py文件在不同的应用场景下执行不同的逻辑
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]))
代码执行 Python flb.py 100
只需要简单了解的Python模块导入搜索路径
内建(build-in) --> sys.path(sys.path是一个列表,而且第一个位置就是当前文件夹)
模块导入的重点-->包的导入
在實際的開發環境中,你不可能一個檔案的程式碼寫到底,當然你也有可能會引用同資料夾中的其他模組,但是你有沒有想過這一個專案不可能是你一個寫的,都是很多人協作開發。這樣就存在這樣的一個問題了;不同的人不可能用一台電腦,也不可能在一個資料夾下面寫寫功能。他們也有自己的程式碼資料夾,然後大家把功能通過介面的方式,提供呼叫。這時候就面臨這不同資料夾的呼叫問題。而這種問題也需要透過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中高階函數以及函數裝飾器的解析的詳細內容。更多資訊請關注PHP中文網其他相關文章!