日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

python高阶函数 动态_Python进阶内容(一)--- 高阶函数 High order function

發布時間:2024/9/27 python 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python高阶函数 动态_Python进阶内容(一)--- 高阶函数 High order function 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

0. 問題

#本文將圍繞這段代碼進行Python中高階函數相關內容的講解#文中所有代碼的兼容性要求為:Python 3.6,IPython 6.1.0

defaddspam(fn):def new(*args):print("spam,spam,spam")return fn(*args)returnnew

@addspamdefuseful(a,b):print(a**2+b**2)if __name__ == "__main__":

useful(1,2)

1. Python中一切皆對象

你已經學習了Python中的list, tuple, dict等內置數據結構,當你執行:alist = [1, 2, 3]時,你就創建了一個List對象,并且用alist這個變量引用它:

當然你也可以自己定義一個類:

classHouse(object):def __init__(self, area, city):

self.area=area

self.city=citydefsell(self, price):

[...]#other code

returnprice#然后創建一個類的對象:

house = House(200, 'Shanghai')

OK,你立馬就在上海有了一套200平米的房子,它有一些屬性(area, city),和一些方法(__init__, self):

2. 函數是第一類對象

和list, tuple, dict以及House instance一樣,當你定義一個函數時,函數也是對象:

deffunc(a, b):return a + b

在全局域,函數對象func(a,b)被函數名func引用著,它接收兩個參數a和b,計算這兩個參數的和作為返回值。

所謂第一類對象,意思是可以用標識符給對象命名,并且對象可以被當作數據處理:例如賦值、作為參數傳遞給函數,或者作為返回值return 等

因此,你完全可以用其他變量名引用這個函數對象:

deffunc(a, b):return a + b

add= func

這樣,你就可以像調用func(1, 2)一樣,通過新的引用add調用函數了:

print(func(1, 2))print(add(1, 2)) #the same as func(1, 2)

或者將函數對象作為參數,傳遞給另一個函數:

deffunc(a, b):return a + bdefcaller_func(f):return f(1, 2)if __name__ == "__main__":print(caller_func(func))

可以看到,

函數對象func(a,b)作為參數傳遞給caller_func(f)函數,傳參過程類似于一個賦值操作f=func;

于是函數對象func(a,b),被caller_func函數作用域中的局部變量f引用,f實際指向了函數func;

當執行return f(1, 2)的時候,相當于執行了return func(1, 2);

因此輸出結果為3。

3. 函數對象 vs 函數調用

無論是把函數賦值給新的標識符,還是作為參數傳遞給新的函數,針對的都是函數對象本身,而不是函數的調用。

用一個更加簡單,但從外觀上看,更容易產生混淆的例子來說明這個問題。例如定義了下面這個函數:

deffunc():return "hello,world"

#然后分別執行兩次賦值:

ref1 = func #將函數對象賦值給ref1

ref2 = func() #調用函數,將函數的返回值("hello,world"字符串)賦值給ref2

#很多初學者會混淆這兩種賦值,通過Python內建的type函數,可以查看一下這兩次賦值的結果:

In [4]: type(ref1)

Out[4]: function

In [5]: type(ref2)

Out[5]: str#可以看到,ref1引用了函數對象本身,而ref2則引用了函數的返回值。

#通過內建的callable函數,可以進一步驗證ref1是可調用的,而ref2是不可調用的:

In [9]: callable(ref1)

Out[9]: True

In [10]: callable(ref2)

Out[10]: False

4. 閉包&LEGB法則

所謂閉包,就是將“組成函數的語句和這些語句的執行環境”打包在一起時得到的對象

聽上去的確有些復雜,還是用一個栗子來幫助理解一下。假設我們在foo.py模塊中做了如下定義:

#foo.py

filename = "foo.py"

defcall_func(f):return f() #如前面介紹的,f引用一個函數對象,然后調用它

在另一個func.py模塊中,寫下了這樣的代碼:

#func.py

import foo #導入foo.py

filename = "func.py"

defshow_filename():return "filename: %s" %filenameif __name__ == "__main__":print(foo.call_func(show_filename))#注意:實際發生調用的位置,是在foo.call_func函數中

#當我們用python func.py命令執行func.py時輸出結果為:

$ python func.py

filename:func.py

盡管foo.py模塊中也定義了同名的filename變量,實際調用show_filename的位置也是在foo.py的call_func內部。但是很顯然show_filename()函數使用的是相同環境(func.py模塊)中定義的那個filename變量。

對于嵌套函數,這一機制則會表現的更加明顯:閉包將會捕捉內層函數執行所需的整個環境

#enclosed.py

importfoodefwrapper():

filename= "enclosed.py"

defshow_filename():return "filename: %s" %filenameprint(foo.call_func(show_filename))if __name__ == "__main__":

wrapper()#輸出:filename: enclosed.py

當代碼執行到show_filename中的return "filename: %s" % filename語句時,解析器按照下面的順序查找filename變量:

Local - 本地函數(show_filename)內部,通過任何方式賦值的,而且沒有被global關鍵字聲明為全局變量的filename變量;

Enclosing - 直接外圍空間(上層函數wrapper)的本地作用域,查找filename變量(如果有多層嵌套,則由內而外逐層查找,直至最外層的函數);

Global - 全局空間(模塊enclosed.py),在模塊頂層賦值的filename變量;

Builtin - 內置模塊(__builtin__)中預定義的變量名中查找filename變量;

在任何一層找到了符合要求的filename變量,則不再向更外層查找。如果直到Builtin層仍沒有找到符合要求的變量,則拋出NameError異常。這就是變量名解析的LEGB法則。

總結:

閉包最重要的使用價值在于:封存函數執行的上下文環境;

閉包在其捕捉的執行環境(def語句塊所在上下文)中,也遵循LEGB規則逐層查找,直至找到符合要求的變量,或者拋出異常。

5. 裝飾器&語法糖(syntax sugar)

那么閉包和裝飾器又有什么關系呢?上文提到閉包的重要特性:封存上下文,這一特性可以巧妙的被用于現有函數的包裝,從而為現有函數增加功能,而這就是裝飾器。還是舉個例子,代碼如下:

importfunctools#我們定義了一個函數lazy_sum,作用是對alist中的所有元素求和后返回。#但是出于某種原因,我并不想馬上返回計算結果,而是在之后的某個地方,通過顯示的調用輸出結果。#于是我用一個wrapper函數對其進行包裝:

defwrapper():

alist= range(1, 101) #alist假設為1到100的整數列表:alist = range(1, 101)

deflazy_sum():return functools.reduce(lambda x, y: x +y, alist)returnlazy_sum

lazy_sum= wrapper() #wrapper() 返回的是lazy_sum函數對象

if __name__ == "__main__":

lazy_sum()#5050

這是一個典型的Lazy Evaluation的例子。我們知道,一般情況下,局部變量在函數返回時,就會被垃圾回收器回收,不能再被使用。但這里的alist卻沒有,它隨著lazy_sum函數對象的返回被一并返回了(這個說法不準確,實際上是通過__globals__屬性,包含在了lazy_sum的執行環境中),從而延長了生命周期。當在if語句塊中調用lazy_sum()的時候,解析器會從上下文中(這里是Enclosing層的wrapper函數的局部作用域中)找到alist列表,計算結果,返回5050。

當你需要動態的給已定義的函數增加功能時,比如參數檢查,類似的原理就變得很有用:

defadd(a, b):return a+b

In [4]: add(5, 'hello')---------------------------------------------------------------------------TypeError Traceback (most recent call last) in ()----> 1 add(5,"hello") inadd(a, b)1 defadd(a, b):----> 2 return a+b

TypeError: unsupported operand type(s)for +: 'int' and 'str'

這是很簡單的一個函數:計算a+b的和返回,但我們知道Python是 動態類型+強類型 的語言,你并不能保證用戶傳入的參數a和b一定是兩個整型,他有可能傳入了一個整型和一個字符串類型的值:解析器無情的拋出了一個TypeError異常。

動態類型:在運行期間確定變量的類型,Python確定一個變量的類型是在你第一次給他賦值的時候;

強類型:有強制的類型定義,你有一個整數,除非顯示的類型轉換,否則絕不能將它當作一個字符串(例如直接嘗試將一個整型和一個字符串做+運算);

因此,為了更加優雅的使用add函數,我們需要在執行求和運算前,對a和b進行參數檢查。這時候裝飾器就顯得非常有用:

#func.py

defadd(a, b):return a +bdefcheckParams(fn):defwrapper(a, b):if isinstance(a, (int, float)) and isinstance(b, (int, float)): #檢查參數a和b是否都為整型或浮點型

return fn(a, b) #是,則調用fn(a, b)返回計算結果

logging.warning("variable 'a' and 'b' cannot be added") #否,則通過logging記錄錯誤信息,并友好退出

return

return wrapper #fn引用add,被封存在閉包的執行環境中返回

if __name__ == "__main__":#將add函數對象傳入,fn指向add

#等號左側的add,指向checkParams的返回值wrapper

add =checkParams(add)

add(3, 'hello') #經過類型檢查,不會計算結果,而是記錄日志并退出

注意checkParams函數:

首先看參數fn,當我們調用checkParams(add)的時候,它將成為函數對象add的一個本地(Local)引用;

在checkParams內部,我們定義了一個wrapper函數,添加了參數類型檢查的功能,然后調用了fn(a, b),根據LEGB法則,解釋器將搜索幾個作用域,并最終在(Enclosing層)checkParams函數的本地作用域中找到fn;

注意最后的return wrapper,這將創建一個閉包,fn變量(add函數對象的一個引用)將會封存在閉包的執行環境中,不會隨著checkParams的返回而被回收;

當調用add = checkParams(add)時,add指向了新的wrapper對象,它添加了參數檢查和記錄日志的功能,同時又能夠通過封存的fn,繼續調用原始的add進行求和運算。因此調用add(3, 'hello')將不會返回計算結果,而是打印出日志:

$ python func.py

WARNING:root:variable'a' and 'b' cannot be added

有人覺得add = checkParams(add)這樣的寫法未免太過麻煩,于是Python提供了一種更優雅的寫法,被稱為語法糖:

importlogging

logging.basicConfig(level=logging.INFO)

@checkParamsdefadd(a, b):return a +bdefcheckParams(fn):defwrapper(a, b):if isinstance(a, (int, float)) and isinstance(b, (int, float)): #檢查參數a和b是否都為整型或浮點型

return fn(a, b) #是,則調用fn(a, b)返回計算結果

logging.warning("variable 'a' and 'b' cannot be added") #否,則通過logging記錄錯誤信息,并友好退出

return

return wrapper #fn引用add,被封存在閉包的執行環境中返回

if __name__ == "__main__":

add(3, 'hello') #經過類型檢查,不會計算結果,而是記錄日志并退出

這只是一種寫法上的優化,解釋器仍然會將它轉化為add = checkParams(add)來執行。

6. 回歸問題

defaddspam(fn):def new(*args):print("spam,spam,spam")return fn(*args)returnnew

@addspamdefuseful(a,b):print(a**2+b**2)if __name__ == "__main__":

useful(1,2)

首先看第二段代碼:

@addspam裝飾器,相當于執行了useful = addspam(useful)。這里 傳遞給addspam的參數是useful這個函數對象本身,而不是它的調用結果;

再回到addspam函數體:

return new 返回一個閉包,fn被封存在閉包的執行環境中,不會隨著addspam函數的返回被回收;

而fn此時是useful的一個引用,當執行return fn(*args)時,實際相當于執行了return useful(*args);

最后附上一張代碼執行過程中的引用關系圖,幫助你進行理解:

From:http://pythontutor.com/visualize.html#mode=edit

From:https://www.zhihu.com/question/25950466

總結

以上是生活随笔為你收集整理的python高阶函数 动态_Python进阶内容(一)--- 高阶函数 High order function的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。