摘要:然后煎魚加了一個(gè)后再調(diào)用函數(shù),得到的輸出結(jié)果和加修飾器的一樣,換言之等效于因此,我們對(duì)于,可以理解是,它通過閉包的方式把新函數(shù)的引用賦值給了原來函數(shù)的引用。
“Python有什么好學(xué)的”這句話可不是反問句,而是問句哦。
主要是煎魚覺得太多的人覺得Python的語法較為簡單,寫出來的代碼只要符合邏輯,不需要太多的學(xué)習(xí)即可,即可從一門其他語言跳來用Python寫(當(dāng)然這樣是好事,誰都希望入門簡單)。
于是我便記錄一下,如果要學(xué)Python的話,到底有什么好學(xué)的。記錄一下Python有什么值得學(xué)的,對(duì)比其他語言有什么特別的地方,有什么樣的代碼寫出來更Pythonic。一路回味,一路學(xué)習(xí)。
什么是修飾器,為什么叫修飾器修飾器英文是Decorator,
我們假設(shè)這樣一種場景:古老的代碼中有幾個(gè)很是復(fù)雜的函數(shù)F1、F2、F3...,復(fù)雜到看都不想看,反正我們就是不想改這些函數(shù),但是我們需要改造加功能,在這個(gè)函數(shù)的前后加功能,這個(gè)時(shí)候我們很容易就實(shí)現(xiàn)這個(gè)需求:
def hi(): """hi func,假裝是很復(fù)雜的函數(shù)""" return "hi" def aop(func): """aop func""" print("before func") print(func()) print("after func") if __name__ == "__main__": aop(hi)
以上是很是簡單的實(shí)現(xiàn),利用Python參數(shù)可以傳函數(shù)引用的特性,就可以實(shí)現(xiàn)了這種類似AOP的效果。
這段代碼目前沒有什么問題,接下來煎魚加需求:需求為幾十個(gè)函數(shù)都加上這樣的前后的功能,而所有調(diào)用這些函數(shù)地方也要相應(yīng)地升級(jí)。
看起來這個(gè)需求比較扯,偏偏這個(gè)需求卻是較為廣泛:在調(diào)用函數(shù)的前后加上log輸出、在調(diào)用函數(shù)的前后計(jì)算調(diào)用時(shí)間、在調(diào)用函數(shù)的前后占用和釋放資源等等。
一種比較笨的方法就是,為這幾十個(gè)函數(shù)逐一添加一個(gè)入口函數(shù),針對(duì)a函數(shù)添加一個(gè)a_aop函數(shù),針對(duì)b函數(shù)添加一個(gè)b_aop函數(shù)...如此這樣。問題也很明顯:
工作量大
代碼變得臃腫復(fù)雜
原代碼有多處調(diào)用了這些函數(shù),可以會(huì)升級(jí)不完全
于是接下來有請(qǐng)修飾器出場,修飾器可以統(tǒng)一地給這些函數(shù)加這樣的功能:
def aop(func): """aop func""" def wrapper(): """wrapper func""" print("before func") func() print("after func") return wrapper @aop def hi(): """hi func""" print("hi") @aop def hello(): """hello func""" print("hello") if __name__ == "__main__": hi() hello()
以上aop函數(shù)就是修飾器的函數(shù),使用該修飾器時(shí)只要在待加函數(shù)上一行加@修飾器函數(shù)名即可,如實(shí)例代碼中就是@aop。
加上了@aop后,調(diào)用新功能的hi函數(shù)就喝原來的調(diào)用一樣:就是hi()而不是aop(hi),也意味著所有調(diào)用這些函數(shù)的地方不需要修改就可以升級(jí)。
簡單地來說,大概修飾器就是以上的這樣子。
@是個(gè)什么對(duì)于新手來說,上面例子中,@就是一樣奇怪的東西:為什么這樣子用就可以實(shí)現(xiàn)煎魚需求的功能了。
其實(shí)我們還可以不用@,煎魚換一種寫法:
def hi(): """hi func""" print("hi") def aop(func): """aop func""" def wrapper(): """wrapper func""" print("before func") func() print("after func") return wrapper if __name__ == "__main__": hi() print("") hi = aop(hi) hi()
上面的例子中的aop函數(shù)就是之前說過的修飾器函數(shù)。
如例子main函數(shù)中第一次調(diào)用hi函數(shù)時(shí),由于hi函數(shù)沒叫修飾器,因此我們可以從輸出結(jié)果中看到程序只輸出了一個(gè)hi而沒有前后功能。
然后煎魚加了一個(gè)hi = aop(hi)后再調(diào)用hi函數(shù),得到的輸出結(jié)果和加修飾器的一樣,換言之:
@aop 等效于hi = aop(hi)
因此,我們對(duì)于@,可以理解是,它通過閉包的方式把新函數(shù)的引用賦值給了原來函數(shù)的引用。
有點(diǎn)拗口。aop(hi)是新函數(shù)的引用,至于返回了引用的原因是aop函數(shù)中運(yùn)用閉包返回了函數(shù)引用。而hi這個(gè)函數(shù)的引用,本來是指向舊函數(shù)的,通過hi = aop(hi)賦值后,就指向新函數(shù)了。
被調(diào)函數(shù)加參數(shù)以上的例子中,我們都假設(shè)被調(diào)函數(shù)是無參的,如hi、hello函數(shù)都是無參的,我們?cè)倏匆谎奂弭~剛才的寫的修飾器函數(shù):
def aop(func): """aop func""" def wrapper(): """wrapper func""" print("before func") func() print("after func") return wrapper
很明顯,閉包函數(shù)wrapper中,調(diào)用被調(diào)函數(shù)用的是func(),是無參的。同時(shí)就意味著,如果func是一個(gè)帶參數(shù)的函數(shù),再用這個(gè)修飾器就會(huì)報(bào)錯(cuò)。
@aop def hi_with_deco(a): """hi func""" print("hi" + str(a)) if __name__ == "__main__": # hi() hi_with_deco(1)
就是參數(shù)的問題。這個(gè)時(shí)候,我們把修飾器函數(shù)改得通用一點(diǎn)即可,其中import了一個(gè)函數(shù)(也是修飾器函數(shù)):
from functools import wraps def aop(func): """aop func""" @wraps(func) def wrap(*args, **kwargs): print("before") func(*args, **kwargs) print("after") return wrap @aop def hi(a, b, c): """hi func""" print("test hi: %s, %s, %s" % (a, b, c)) @aop def hello(a, b): """hello func""" print("test hello: %s, %s" % (a, b)) if __name__ == "__main__": hi(1, 2, 3) hello("a", "b")
這是一種很奇妙的東西,就是在寫修飾器函數(shù)的時(shí)候,還用了別的修飾器函數(shù)。那也沒什么,畢竟修飾器函數(shù)也是函數(shù)啊,有什么所謂。
帶參數(shù)的修飾器思路到了這里,煎魚不禁思考一個(gè)問題:修飾器函數(shù)也是函數(shù),那函數(shù)也是應(yīng)該能傳參的。函數(shù)傳參的話,不同的參數(shù)可以輸出不同的結(jié)果,那么,修飾器函數(shù)傳參的話,不同的參數(shù)會(huì)怎么樣呢?
其實(shí)很簡單,修飾器函數(shù)不同的參數(shù),能生成不同的修飾器啊。
如,我這次用這個(gè)修飾器是把時(shí)間日志打到test.log,而下次用修飾器的時(shí)候煎魚希望是能打到test2.log。這樣的需求,除了寫兩個(gè)修飾器函數(shù)外,還可以給修飾器加參數(shù)選項(xiàng):
from functools import wraps def aop_with_param(aop_test_str): def aop(func): """aop func""" @wraps(func) def wrap(*args, **kwargs): print("before " + str(aop_test_str)) func(*args, **kwargs) print("after " + str(aop_test_str)) return wrap return aop @aop_with_param("abc") def hi(a, b, c): """hi func""" print("test hi: %s, %s, %s" % (a, b, c)) @aop_with_param("pppppp") def hi2(a, b, c): """hi func""" print("test hi: %s, %s, %s" % (a, b, c)) if __name__ == "__main__": hi(1, 2, 3) print("") hi2(2, 3, 4)
同樣的,可以加一個(gè)參數(shù),也可以加多個(gè)參數(shù),這里就不說了。
修飾器類大道同歸,邏輯復(fù)雜了之后,人們都喜歡將函數(shù)的思維層面抽象上升到對(duì)象的層面。原因往往是對(duì)象能擁有多個(gè)函數(shù),對(duì)象往往能管理更復(fù)雜的業(yè)務(wù)邏輯。
顯然,修飾器函數(shù)也有對(duì)應(yīng)的修飾器類。寫起來也沒什么難度,和之前的生成器一樣簡單:
from functools import wraps class aop(object): def __init__(self, aop_test_str): self.aop_test_str = aop_test_str def __call__(self, func): @wraps(func) def wrapper(*args, **kwargs): print("before " + self.aop_test_str) func() print("after " + self.aop_test_str) return wrapper @aop("pppppp") def hi(): print("hi")
看得出來,這個(gè)修飾器類也不過是多了個(gè)__call__函數(shù),而這個(gè)__call__函數(shù)的內(nèi)容和之前寫的修飾器函數(shù)一個(gè)樣!而使用這個(gè)修飾器的方法,和之前也一樣,一樣的如例子中的@aop("pppppp")。
甚至,煎魚過于無聊,還試了一下繼承的修飾器類:
class sub_aop(aop): def __init__(self, sub_aop_str, *args, **kwargs): self.sub_aop_str = sub_aop_str super(sub_aop, self).__init__(*args, **kwargs) def __call__(self, func): @wraps(func) def wrapper(*args, **kwargs): print("before " + self.sub_aop_str) super(sub_aop, self).__call__(func)() print("after " + self.sub_aop_str) return wrapper @sub_aop("ssssss", "pppppp") def hello(): print("hello") if __name__ == "__main__": hello()
你們猜猜結(jié)果怎么樣?
先這樣吧
若有錯(cuò)誤之處請(qǐng)指出,更多地請(qǐng)關(guān)注造殼。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/44953.html
摘要:引上下文管理器太極生兩儀,兩儀為陰陽。而最常用的則是,即上下文管理器使用上下文管理器用之后的文件讀寫會(huì)變成我們看到用了之后,代碼沒有了創(chuàng)建,也沒有了釋放。實(shí)現(xiàn)上下文管理器我們先感性地對(duì)進(jìn)行猜測?,F(xiàn)實(shí)一個(gè)上下文管理器就是這么簡單。 Python有什么好學(xué)的這句話可不是反問句,而是問句哦。 主要是煎魚覺得太多的人覺得Python的語法較為簡單,寫出來的代碼只要符合邏輯,不需要太多的學(xué)習(xí)即可...
摘要:為什么是斐波那契談到生成器迭代器,人們總是喜歡用斐波那契數(shù)列來舉例。那么,換句話來說,即能由推導(dǎo)式得出的數(shù)列,其實(shí)都可以用來做生成器迭代器的例子。然而,生成器和生成器類的實(shí)例都屬于迭代器。 Python有什么好學(xué)的這句話可不是反問句,而是問句哦。 主要是煎魚覺得太多的人覺得Python的語法較為簡單,寫出來的代碼只要符合邏輯,不需要太多的學(xué)習(xí)即可,即可從一門其他語言跳來用Python寫...
一、前提概念 Python中的函數(shù)是對(duì)象。也因此,函數(shù)可以被當(dāng)做變量使用。 二、代碼模型 以下代碼片段來自于: http://www.sharejs.com/codes/python/8361 # -*- coding: utf-8 -*- from threading import Thread import time class TimeoutEx...
摘要:常見的八大排序算法,他們之間關(guān)系如下被人忽視的面向?qū)ο蟮牧笤瓌t后端掘金前言作為文集的第一篇,我覺得有必要介紹一下大概的寫作規(guī)劃。 Java多線程干貨系列—(四)volatile關(guān)鍵字| 掘金技術(shù)征文 - 掘金原本地址:Java多線程干貨系列—(四)volatile關(guān)鍵字博客地址:http://tengj.top/ 前言 今天介紹下volatile關(guān)鍵字,volatile這個(gè)關(guān)鍵字可能...
閱讀 2109·2023-04-26 02:41
閱讀 2153·2021-09-24 09:47
閱讀 1560·2019-08-30 15:53
閱讀 1214·2019-08-30 13:01
閱讀 1894·2019-08-29 11:27
閱讀 2868·2019-08-28 17:55
閱讀 1778·2019-08-26 14:00
閱讀 3393·2019-08-26 10:18