一位Python初学者的自白:Python小白眼中的装饰器
這是在微信看到的一篇文章,伙伴剛學(xué)Python不久,我認(rèn)真看了一下,確實(shí)是很真實(shí)的剛?cè)腴TPython學(xué)習(xí)的伙伴!我把這篇文章拿到這來,也希望更多剛接觸Python的伙伴能有所學(xué),有所悟!
原文如下:
hello,大家好,首先介紹一下,我并不是一名真正意義上的程序猿。最近在學(xué)習(xí)Python這門語言,希望有個(gè)地方可以記錄爬過的坑。站在研發(fā)鄙視鏈最底端,也是想通過一些手段來減少重復(fù)性工作來提高效率。那Python這門語言就能很好的幫我解決這些問題。今天就帶你認(rèn)識(shí)一下小白眼中的“裝飾器”。
小白學(xué)習(xí),概念是必須*3要學(xué)習(xí)并理解的!!那什么是裝飾器,裝飾器又可以做些什么呢!?
首先概念,裝飾器是閉包的一種應(yīng)用(閉包后面會(huì)講到,本次不做闡述),需要滿足一下規(guī)則:
1.在不更改原功能函數(shù)的內(nèi)部代碼,并且改變調(diào)用方法的情況下為原函數(shù)增加新功能
2.遵循開放封閉原則,什么是開放封閉原則呢?
a.已實(shí)現(xiàn)的功能可以添加或擴(kuò)展新的功能(開放原則)
b.不修改已實(shí)現(xiàn)功能的內(nèi)部代碼(封閉原則)
其次作用,登錄驗(yàn)證、函數(shù)運(yùn)行時(shí)長統(tǒng)計(jì)、執(zhí)行函數(shù)之前做的準(zhǔn)備工作,執(zhí)行函數(shù)之后清理功能,總之你能想到的擴(kuò)展功能大部分都可以實(shí)現(xiàn),而且是在原功能代碼不做修改的情況下就可以優(yōu)雅的完成!
一起來看下裝飾器的實(shí)踐,親身經(jīng)歷的問題!因?yàn)楣ぷ鳟?dāng)中經(jīng)常性需要獲取接口數(shù)據(jù),所以就簡單的封裝了一個(gè)獲取數(shù)據(jù)的方法,代碼如下:
import requests import re def send_request_by(method, url, data):"""請(qǐng)求接口獲取數(shù)據(jù):param method: 發(fā)起請(qǐng)求的方式:param url: 請(qǐng)求地址:param data: 請(qǐng)求數(shù)據(jù):return:"""if re.match("POST", method, flags=re.IGNORECASE):response = requests.post(url, data=data)if re.match("GET", method, flags=re.IGNORECASE):response = requests.get(url, data=data)return response 復(fù)制代碼目前看來對(duì)自己的需求已經(jīng)滿足,但是每次請(qǐng)求的時(shí)候發(fā)現(xiàn)還是報(bào)錯(cuò)!最終通過抓包工具分析發(fā)現(xiàn),在客戶端進(jìn)行接口調(diào)用的時(shí)都多了一個(gè)"sign"字段,這個(gè)字段是怎么來的呢?經(jīng)過分析"sign"是在加密后得來的,為了解決這個(gè)問題對(duì)代碼進(jìn)行了一次改造,代碼如下:
def send_request_by(method, url, data):md5_pwd = MD5Password()data['sign'] = md5_pwd(data)if re.match("POST", method, flags=re.IGNORECASE):response = requests.post(url, data=data)if re.match("GET", method, flags=re.IGNORECASE):response = requests.get(url, data=data)return response 復(fù)制代碼這樣看起來是解決了問題,但其實(shí)沒有靈活的解決問題。試問,如果哪天又不需要加密簽名是不是還得把新加的兩行代碼干掉?那怎么能不修改原功能又能添加加密的功能呢。經(jīng)過和大佬討教之后,發(fā)現(xiàn)可以通過裝飾器來實(shí)現(xiàn),再一次對(duì)代碼進(jìn)行了改造,代碼如下:
def sign_md5(func):def wrapper(*args, **kwargs):if not kwargs.get('data'):raise KeyError("not found Key 'data'")if kwargs.get('data') is None:raise ValueError(f'{kwargs.get("data")} of value is None')# 首字母排序sort_data = json.loads(json.dumps(kwargs.get('data'), sort_keys=True))# 私有加密規(guī)則,生成簽名md5_pwd = MD5Password()sign = md5_pwd(sort_data)sort_data['sig'] = signkwargs['data'] = sort_dataret = func(*args, **kwargs)return retreturn wrapper@sign_md5 # send_request_by = sign_md5(send_request_by)def send_request_by(method, url, data):# md5_pwd = MD5Password()# data['sign'] = md5_pwd(data)if re.match("POST", method, flags=re.IGNORECASE):response = requests.post(url, data=data)if re.match("GET", method, flags=re.IGNORECASE):response = requests.get(url, data=data)return response 復(fù)制代碼經(jīng)過測(cè)試之后發(fā)現(xiàn),非常靈活的解決問題,不需要加密的時(shí)候注釋掉@sign_md5即可!那么這個(gè)@sign_md5到底做了什么?
其實(shí)質(zhì)就是在沒有使用"@"魔法的情況下是sign_md5(send_request_by)(method, ulr, data),而當(dāng)使用"@"魔法進(jìn)行裝飾后,代碼執(zhí)行到此行時(shí)解析器會(huì)將被裝飾的send_request_by作為一個(gè)參數(shù)傳遞給sign_md5,即send_request_by這個(gè)函數(shù)已經(jīng)作為變量傳遞給了sign_md(func)中的func參數(shù),并返回了wrapper這個(gè)函數(shù),在接下來調(diào)用send_request_by(method, url, data)這個(gè)函數(shù),其實(shí)此時(shí)send_request_by已經(jīng)不是原先的def send_request_by(method, url, data)中的send_request_by,而是指向了wrapper(*args, **kwargs)這個(gè)函數(shù)。wrapper這個(gè)函數(shù)接收任意參數(shù),所以當(dāng)執(zhí)行send_request_by(method, url, data)函數(shù)時(shí),其實(shí)把method, url, data參數(shù)傳遞給warpper函數(shù),并執(zhí)行wrapper內(nèi)部代碼,而wrapper函數(shù)內(nèi)部的func就是我們傳入的send_request_by函數(shù)了,意思可以理解為,我們將參數(shù)傳給了wrapper函數(shù),在wrapper函數(shù)內(nèi)將請(qǐng)求參數(shù)進(jìn)行加密后,傳遞給了send_request_by函數(shù)進(jìn)行請(qǐng)求,從而在請(qǐng)求之前完成了加密的過程。而wrapper函數(shù)內(nèi)func(*args, **kwargs)指向的是send_request_by(method, url, data)而ret也就是send_request_by(method, url, data)函數(shù)的返回結(jié)果,因此將ret返回出來。這塊比較繞口,多捋一捋相信憑你的聰明才智一定會(huì)明白!到時(shí)你一定會(huì)認(rèn)為,哇!竟然如此簡單!!!而到此為止,最簡單的無參數(shù)裝飾器就此完成!領(lǐng)導(dǎo)一定會(huì)夸你,秀兒!
那么,在解決了這個(gè)需求之后自己又有了新的疑惑,如果哪天開發(fā)不按照字段首字母排序了,我該怎么辦!??是不是又要重新寫裝飾器了???那有什么辦法能在裝飾器內(nèi)控制是否排序呢?那么也是經(jīng)過各種腦補(bǔ),又又又進(jìn)行了一次代碼的修改,代碼如下:
def sign_sort(sort=True):def sign_md5(func):def wrapper(*args, **kwargs):if not kwargs.get('data'):raise KeyError("not found Key 'data'")if kwargs.get('data') is None:raise ValueError(f'{kwargs.get("data")} of value is None')# sort 參數(shù)控制是否按首字母排序sort_data = json.dumps(kwargs.get('data'), sort_keys=True) if sort else json.dumps(kwargs.get('data'))sort_data = json.loads(sort_data)# 私有加密規(guī)則,生成簽名md5_pwd = MD5Password()sign = md5_pwd(sort_data)sort_data['sig'] = signkwargs['data'] = sort_dataret = func(*args, **kwargs)return retreturn wrapperreturn sign_md5 @sign_sort(sort=True) # send_request_by = sign_sort(sort=Ture)(send_request_by) def send_request_by(method, url, data):print(data)if re.match("POST", method, flags=re.IGNORECASE):response = requests.post(url, data=data)elif re.match("GET", method, flags=re.IGNORECASE):response = requests.get(url, data=data)return respons 復(fù)制代碼那這一次是做了哪些優(yōu)化呢?其實(shí)還是在學(xué)習(xí)無參裝飾器的基礎(chǔ)之上學(xué)習(xí)了一下帶參數(shù)裝飾器。仍然還是不變的配方。我們一起來分析一下,
首先來看sign_sort(sort=True),sign_sort實(shí)質(zhì)是一個(gè)函數(shù)接收一個(gè)參數(shù),并返回sign_md5函數(shù)。
當(dāng)代碼執(zhí)行到"@"所在行時(shí),同樣會(huì)把被裝飾send_request_by函數(shù)作為一個(gè)參數(shù)傳遞給sign_sort(sort=True)函數(shù)的調(diào)用結(jié)果(即sign_md5)。
也就是說send_request_by函數(shù)又是作為一個(gè)變量(函數(shù)也可以是變量)傳遞給了sign_md5函數(shù)中的func參數(shù),并返回了一個(gè)wrapper函數(shù)。
在后面調(diào)用send_request_by(method, url, data)函數(shù)時(shí),同樣此時(shí)的send_request_by已經(jīng)不再是def send_request_by(method, url, data)函數(shù)中的send_request_by,而是wrapper(*args, **kwargs)函數(shù)。
wrapper函數(shù)接收任意參數(shù),所以當(dāng)執(zhí)行send_request_by(method, url, data)時(shí),會(huì)把method, url, data傳遞給wrapper函數(shù),執(zhí)行wrapper函數(shù)內(nèi)的代碼,而wrapper內(nèi)部的func(*args, **kwargs)還是指向send_request_by,可以理解為通過wrapper函數(shù)將參數(shù)傳遞給send_request_by函數(shù),而在傳遞給send_request_by之前,我們可以對(duì)參數(shù)做任意的操作,因此我在傳遞之前判斷了sort參數(shù)是否為True作為排序的開關(guān),再對(duì)請(qǐng)求數(shù)據(jù)data進(jìn)行加密的操作,最后帶著加密簽名傳遞給send_request_by函數(shù)進(jìn)行發(fā)送請(qǐng)求。是不是很神奇,也很簡單!?其實(shí)裝飾器并沒有想象中的那么難理解,只是有一點(diǎn)點(diǎn)繞口,需要能夠分析函數(shù)的指向,這樣就能夠輕松駕馭“裝飾器”,使你的代碼更加的優(yōu)雅!!老板都不忍心不給你加薪!!!
寫到這里,Python中的“魔法”之一,函數(shù)裝飾器也就差不多講完了,其實(shí)裝飾器同樣可以寫成類裝飾器,以及更為廣泛的用法,是我這個(gè)小白還沒有接觸到的~~希望能夠得到大佬的點(diǎn)(怒)撥(懟)!
至于Python當(dāng)中的“魔法”還有很多很多,比如說上面代碼中,md5_pwd = MD5Password明明是一個(gè)對(duì)象,為什么還能夠像函數(shù)(md5_pwd())一樣調(diào)用,以及如何寫一個(gè)類裝飾器呢?這些功能都是通過Python底層的魔術(shù)__call__方法來實(shí)現(xiàn)的!那么下一次的主題,就是__call__方法,會(huì)講述我對(duì)__call__方法的初識(shí),以及重新認(rèn)識(shí)!
最后,文章中肯定有講述不對(duì)的地方,歡迎各種代碼大佬來懟我,教育我,指正我!!因?yàn)槲艺娴南雽W(xué)好Python!!
我的愿景是立志于做一名開發(fā)、測(cè)試一條龍的工程師!代碼越寫越優(yōu)雅!(然后,和女朋友接私活~~~hhhh)很真實(shí)的小伙伴了啊,畢竟曾經(jīng)我也有過這樣的想法,哈哈哈!大家在剛接觸Python的時(shí)候是怎樣的心情和感悟呢!也可以留言哈!
文章原創(chuàng): xiaoanzi 種豆兒得瓜
轉(zhuǎn)載于:https://juejin.im/post/5d1d7766f265da1bb2774e51
總結(jié)
以上是生活随笔為你收集整理的一位Python初学者的自白:Python小白眼中的装饰器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python爬虫抖某音爬取视频 Air
- 下一篇: websocket python爬虫_p