在Python語言中,可以在函數(shù)中定義函數(shù)。 這種在函數(shù)中嵌套定義的函數(shù)也叫內(nèi)部函數(shù)。我們來看下面的代碼:
成都創(chuàng)新互聯(lián)公司是專業(yè)的永新網(wǎng)站建設(shè)公司,永新接單;提供成都網(wǎng)站制作、成都網(wǎng)站建設(shè),網(wǎng)頁設(shè)計,網(wǎng)站設(shè)計,建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進(jìn)行永新網(wǎng)站開發(fā)網(wǎng)頁制作和功能擴(kuò)展;專業(yè)做搜索引擎喜愛的網(wǎng)站,專業(yè)的做網(wǎng)站團(tuán)隊,希望更多企業(yè)前來合作!
上述代碼中,定義了函數(shù)greet,在函數(shù)greet內(nèi)部又定義了一個函數(shù)inner_func, 并調(diào)用該函數(shù)打印了一串字符。
我們可以看到,內(nèi)部函數(shù)inner_func的定義和使用與普通函數(shù)基本相同。需要注意的是變量的作用域,在上述代碼中,函數(shù)參數(shù)name對于全局函數(shù)greet是局部變量,對內(nèi)部函數(shù)inner_func來說則是非局部變量。內(nèi)部函數(shù)對于非局部變量的訪問規(guī)則類似于標(biāo)準(zhǔn)的外部函數(shù)訪問全局變量。
從這個例子我們還可以看到內(nèi)部函數(shù)的一個作用,就是通過定義內(nèi)部函數(shù)的方式將一些功能隱藏起來,防止外部直接調(diào)用。常見的場景是,在一個復(fù)雜邏輯的函數(shù)中,將一些小的任務(wù)定義成內(nèi)部函數(shù),然后由這個外層函數(shù)使用,這樣可以使代碼更為清晰,易于維護(hù)。這些內(nèi)部函數(shù)只會在這個外層函數(shù)中使用,不能被其他函數(shù)或模塊使用。
在Python語言中, 函數(shù)也是對象,它可以被創(chuàng)建、賦值給變量,或者作為函數(shù)的返回值。我們來看下面這個例子。
在上述代碼中,在函數(shù)gen_greet內(nèi)部定義了inner_func函數(shù),并返回了一個inner_func函數(shù)對象。外部函數(shù)gen_greet返回了一個函數(shù)對象,所以像gen_greet這樣的函數(shù)也叫工廠函數(shù)。
在內(nèi)部函數(shù)inner_func中,使用了外部函數(shù)的傳參greet_words(非局部變量),以及函數(shù)的參數(shù)name(局部變量),來打印一個字符串。
接下來,調(diào)用gen_greet("Hello")創(chuàng)建一個函數(shù)對象say_hello,緊接著調(diào)用say_hello("Mr. Zhang"),輸出的結(jié)果為:Hello, Mr. Zhang!
同樣的,調(diào)用gen_greet("Hi")創(chuàng)建一個函數(shù)對象say_hi,調(diào)用say_hello("Mr. Zhang"),輸出的結(jié)果為:Hi,Tony!
我們可以發(fā)現(xiàn),gen_greet返回的函數(shù)對象具有記憶功能,它能夠把所需使用的非局部變量保存下來,用于后續(xù)被調(diào)用的時候使用。這種保存了非局部變量的函數(shù)對象被稱作閉包(closure)。
那么閉包是如何實現(xiàn)的呢?其實并不復(fù)雜,函數(shù)對象中有一個屬性__closure__,它就是在創(chuàng)建函數(shù)對象時用來保存這些非局部變量的。
__closure__屬性是一個元組或者None類型。在上述代碼中,我們可以通過下面方式查看:
函數(shù)的嵌套所實現(xiàn)的功能大都可以通過定義類的方式來實現(xiàn),而且類是更加面向?qū)ο蟮拇a編寫方式。
嵌套函數(shù)的一個主要用途是實現(xiàn)函數(shù)的裝飾器。我們看下面的代碼:
在上述代碼中,logger函數(shù)返回函數(shù)with_logging,with_logging則是打印了函數(shù)func的名稱及傳入的參數(shù),然后調(diào)用func, 并將參數(shù)傳遞給func。其中的@wraps(func)語句用于復(fù)制函數(shù)func的名稱、注釋文檔、參數(shù)列表等等,使得with_logging函數(shù)具有被裝飾的函數(shù)func相同的屬性。
代碼中接下來用@logger對函數(shù)power_func進(jìn)行修飾,它的作用等同于下面的代碼:
可見,裝飾器@符其實就是上述代碼的精簡寫法。
通過了解了嵌套函數(shù)和閉包的工作原理,我們在使用過程中就能夠更加得心應(yīng)手了。
循環(huán)是一種常用的程序控制結(jié)構(gòu)。我們常說,機(jī)器相比人類的最大優(yōu)點之一,就是機(jī)器可以不眠不休的重復(fù)做某件事情,但人卻不行。而“循環(huán)”,則是實現(xiàn)讓機(jī)器不斷重復(fù)工作的關(guān)鍵概念。
在循環(huán)語法方面,Python 表現(xiàn)的即傳統(tǒng)又不傳統(tǒng)。它雖然拋棄了常見的 for(init;condition;incrment) 三段式結(jié)構(gòu),但還是選擇了 for 和 while 這兩個經(jīng)典的關(guān)鍵字來表達(dá)循環(huán)。絕大多數(shù)情況下,我們的循環(huán)需求都可以用 forin來滿足, while相比之下用的則更少些。
雖然循環(huán)的語法很簡單,但是要寫好它確并不容易。在這篇文章里,我們將探討什么是“地道”的循環(huán)代碼,以及如何編寫它們。
什么是“地道”的循環(huán)?
“地道”這個詞,通常被用來形容某人做某件事情時,非常符合當(dāng)?shù)貍鹘y(tǒng),做的非常好。打個比方,你去參加一個朋友聚會,同桌的有一位廣東人,對方一開口,句句都是標(biāo)準(zhǔn)京腔、完美兒化音。那你可以對她說:“您的北京話說的真地道”。
既然“地道”這個詞形容的經(jīng)常是口音、做菜的口味這類實實在在的東西,那“地道”的循環(huán)代碼又是什么意思呢?讓我拿一個經(jīng)典的例子來解釋一下。
如果你去問一位剛學(xué)習(xí) Python 一個月的人:“如何在遍歷一個列表的同時獲取當(dāng)前下標(biāo)?”。他可能會交出這樣的代碼:
?
上面的循環(huán)雖然沒錯,但它確一點都不“地道”。一個擁有三年 Python 開發(fā)經(jīng)驗的人會說,代碼應(yīng)該這么寫:
enumerate() 是 Python 的一個內(nèi)置函數(shù),它接收一個“可迭代”對象作為參數(shù),然后返回一個不斷生成 (當(dāng)前下標(biāo),當(dāng)前元素) 的新可迭代對象。這個場景使用它最適合不過。
所以,在上面的例子里,我們會認(rèn)為第二段循環(huán)代碼比第一段更“地道”。因為它用更直觀的代碼,更聰明的完成了工作。
enumerate() 所代表的編程思路
不過,判斷某段循環(huán)代碼是否地道,并不僅僅是以知道或不知道某個內(nèi)置方法作為標(biāo)準(zhǔn)。我們可以從上面的例子挖掘出更深層的東西。
如你所見,Python 的 for 循環(huán)只有 forin這一種結(jié)構(gòu),而結(jié)構(gòu)里的前半部分 - 賦值給 item- 沒有太多花樣可玩。所以后半部分的 可迭代對象 是我們唯一能夠大做文章的東西。而以 enumerate() 函數(shù)為代表的“修飾函數(shù)”,剛好提供了一種思路:通過修飾可迭代對象來優(yōu)化循環(huán)本身。
這就引出了我的第一個建議。
建議1:使用函數(shù)修飾被迭代對象來優(yōu)化循環(huán)
使用修飾函數(shù)處理可迭代對象,可以在各種方面影響循環(huán)代碼。而要找到合適的例子來演示這個方法,并不用去太遠(yuǎn),內(nèi)置模塊 itertools 就是一個絕佳的例子。
簡單來說,itertools 是一個包含很多面向可迭代對象的工具函數(shù)集。我在之前的系列文章《容器的門道》里提到過它。
如果要學(xué)習(xí) itertools,那么 Python 官方文檔 是你的首選,里面有非常詳細(xì)的模塊相關(guān)資料。但在這篇文章里,側(cè)重點將和官方文檔稍有不同。我會通過一些常見的代碼場景,來詳細(xì)解釋它是如何改善循環(huán)代碼的。
1. 使用 product 扁平化多層嵌套循環(huán)
雖然我們都知道“扁平的代碼比嵌套的好”。但有時針對某類需求,似乎一定得寫多層嵌套循環(huán)才行。比如下面這段:
?
對于這種需要嵌套遍歷多個對象的多層循環(huán)代碼,我們可以使用 product() 函數(shù)來優(yōu)化它。product() 可以接收多個可迭代對象,然后根據(jù)它們的笛卡爾積不斷生成結(jié)果。
?
相比之前的代碼,使用 product() 的函數(shù)只用了一層 for 循環(huán)就完成了任務(wù),代碼變得更精煉了。
2. 使用 islice 實現(xiàn)循環(huán)內(nèi)隔行處理
有一份包含 Reddit 帖子標(biāo)題的外部數(shù)據(jù)文件,里面的內(nèi)容格式是這樣的:
?
可能是為了美觀,在這份文件里的每兩個標(biāo)題之間,都有一個 "---" 分隔符?,F(xiàn)在,我們需要獲取文件里所有的標(biāo)題列表,所以在遍歷文件內(nèi)容的過程中,必須跳過這些無意義的分隔符。
參考之前對 enumerate() 函數(shù)的了解,我們可以通過在循環(huán)內(nèi)加一段基于當(dāng)前循環(huán)序號的 if 判斷來做到這一點:
?
但對于這類在循環(huán)內(nèi)進(jìn)行隔行處理的需求來說,如果使用 itertools 里的 islice() 函數(shù)修飾被循環(huán)對象,可以讓循環(huán)體代碼變得更簡單直接。
islice(seq,start,end,step) 函數(shù)和數(shù)組切片操作( list[start:stop:step] )有著幾乎一模一樣的參數(shù)。如果需要在循環(huán)內(nèi)部進(jìn)行隔行處理的話,只要設(shè)置第三個遞進(jìn)步長參數(shù) step 值為 2 即可(默認(rèn)為 1)。
?
3. 使用 takewhile 替代 break 語句
有時,我們需要在每次循環(huán)開始時,判斷循環(huán)是否需要提前結(jié)束。比如下面這樣:
?
對于這類需要提前中斷的循環(huán),我們可以使用 takewhile() 函數(shù)來簡化它。takewhile(predicate,iterable)會在迭代 iterable 的過程中不斷使用當(dāng)前對象作為參數(shù)調(diào)用 predicate 函數(shù)并測試返回結(jié)果,如果函數(shù)返回值為真,則生成當(dāng)前對象,循環(huán)繼續(xù)。否則立即中斷當(dāng)前循環(huán)。
使用 takewhile 的代碼樣例:
?
itertools 里面還有一些其他有意思的工具函數(shù),他們都可以用來和循環(huán)搭配使用,比如使用 chain 函數(shù)扁平化雙層嵌套循環(huán)、使用 zip_longest 函數(shù)一次同時循環(huán)多個對象等等。
篇幅有限,我在這里不再一一介紹。如果有興趣,可以自行去官方文檔詳細(xì)了解。
4. 使用生成器編寫自己的修飾函數(shù)
除了 itertools 提供的那些函數(shù)外,我們還可以非常方便的使用生成器來定義自己的循環(huán)修飾函數(shù)。
讓我們拿一個簡單的函數(shù)舉例:
?
在上面的函數(shù)里,循環(huán)體內(nèi)為了過濾掉所有奇數(shù),引入了一條額外的 if 判斷語句。如果要簡化循環(huán)體內(nèi)容,我們可以定義一個生成器函數(shù)來專門進(jìn)行偶數(shù)過濾:
?
將 numbers 變量使用 even_only 函數(shù)裝飾后, sum_even_only_v2 函數(shù)內(nèi)部便不用繼續(xù)關(guān)注“偶數(shù)過濾”邏輯了,只需要簡單完成求和即可。
Hint:當(dāng)然,上面的這個函數(shù)其實并不實用。在現(xiàn)實世界里,這種簡單需求最適合直接用生成器/列表表達(dá)式搞定:sum(numfornuminnumbersifnum%2==0)
建議2:按職責(zé)拆解循環(huán)體內(nèi)復(fù)雜代碼塊
我一直覺得循環(huán)是一個比較神奇的東西,每當(dāng)你寫下一個新的循環(huán)代碼塊,就好像開辟了一片黑魔法陣,陣內(nèi)的所有內(nèi)容都會開始無休止的重復(fù)執(zhí)行。
但我同時發(fā)現(xiàn),這片黑魔法陣除了能帶來好處,它還會引誘你不斷往陣內(nèi)塞入越來越多的代碼,包括過濾掉無效元素、預(yù)處理數(shù)據(jù)、打印日志等等。甚至一些原本不屬于同一抽象的內(nèi)容,也會被塞入到同一片黑魔法陣內(nèi)。
python函數(shù)修飾符@ 修飾符 ‘@’符號用作函數(shù)修飾符是python2.4新增加的功能,修飾符必須出現(xiàn)在函數(shù)定義前一行,不允許和函數(shù)定義在同一行。也就是說@A def f(): 是非法的。 只可以在模塊或類定義層內(nèi)對函數(shù)進(jìn)行修飾,不允許修修飾一個類。一個修飾符就是一個函數(shù),它將被修飾的函數(shù)做為參數(shù),并返回修飾后的同名函數(shù)或其它可調(diào)用的東西。 本質(zhì)上講,裝飾符@類似于 回調(diào)函數(shù) ,把其它的函數(shù)(暫且稱為目的參數(shù),后面緊接著的函數(shù))作為自己的入?yún)?,在目的函?shù)執(zhí)行前,執(zhí)行一些自己的操作, 比如:計數(shù)、打印一些提示信息等,然后返回目的函數(shù)。下面列舉一個簡單的例子。
創(chuàng)建函數(shù)修飾符的規(guī)則:
(1)修飾符是一個函數(shù)
(2)修飾符取被修飾函數(shù)為參數(shù)
(3)修飾符返回一個新函數(shù)
(4)修飾符維護(hù)被維護(hù)函數(shù)的簽名
例子1: 被修飾函數(shù)不帶參數(shù)
運行結(jié)果:
例子2: 使用functools模塊提供的修改函數(shù)屬性的方法wraps
運行結(jié)果:
可見test1的函數(shù)名稱變了,如果某些代碼用到就會出問題,可以使用functools模塊提供的修改函數(shù)屬性的方法wraps
運行結(jié)果:
例子3: 被修飾函數(shù)帶參數(shù)
運行結(jié)果:
例子4: 修飾符帶參數(shù) ,需要比上面例子多一層包裝
運行結(jié)果:
#Python
2.5
#這個可以用修飾器來完成
#但是一般不會限制參數(shù)類型
#給你個思路:
def
argfilter(*types):
def
deco(func):
#這是修飾器
def
newfunc(*args):
#新的函數(shù)
if
len(types)==len(args):
correct
=
True
for
i
in
range(len(args)):
if
not
isinstance(args[i],
types[i]):
#判斷類型
correct
=
False
if
correct:
return
func(*args)
#返回原函數(shù)值
else:
raise
TypeError
else:
raise
TypeError
return
newfunc
#由修飾器返回新的函數(shù)
return
deco
#返回作為修飾器的函數(shù)
@argfilter(int,
str)
#指定參數(shù)類型
def
func(i,
s):
#定義被修飾的函數(shù)
i,
s
#之后你想限制類型的話,
就這樣:
#@argfilter(第一個參數(shù)的類名,
第二個參數(shù)的類名,
...,
第N個參數(shù)的類名)
#def
yourfunc(第一個參數(shù),
第一個參數(shù),
...,
第N個參數(shù)):
#
...
#
#相當(dāng)于:
#def
yourfunc(第一個參數(shù),
第一個參數(shù),
...,
第N個參數(shù)):
#
...
#yourfunc
=
argfilter(第一個參數(shù)的類名,
第二個參數(shù)的類名,
...,
第N個參數(shù)的類名)(yourfunc)
網(wǎng)站名稱:修飾函數(shù)python的簡單介紹
文章來源:http://jinyejixie.com/article32/dodppsc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供自適應(yīng)網(wǎng)站、品牌網(wǎng)站建設(shè)、全網(wǎng)營銷推廣、小程序開發(fā)、外貿(mào)建站、ChatGPT
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)