了解裝飾器之前睁蕾,
可以先了解一下什么是閉包的概念為好:
閉包,
是指在一個函數(shù)中定義了一個另外一個函數(shù)桶至,內(nèi)函數(shù)里運(yùn)用了外函數(shù)的臨時變量(實(shí)際參數(shù)也是臨時變量)具壮,并且外函數(shù)的返回值是內(nèi)函數(shù)的引用(一切皆引用,所有的函數(shù)名字都只是函數(shù)體在內(nèi)存空間的一個引用馆类。)
通俗的解釋:在一個外函數(shù)中定義了一個內(nèi)函數(shù)混聊,內(nèi)函數(shù)里運(yùn)用了外函數(shù)的臨時變量弹谁,并且外函數(shù)的返回值是內(nèi)函數(shù)的引用乾巧。這樣就構(gòu)成了一個閉包。
一般情況下预愤,在我們認(rèn)知當(dāng)中沟于,如果一個函數(shù)結(jié)束,函數(shù)的內(nèi)部所有東西都會釋放掉植康,還給內(nèi)存旷太,局部變量都會消失。但是閉包是一種特殊情況,如果外函數(shù)在結(jié)束的時候發(fā)現(xiàn)有自己的臨時變量將來會在內(nèi)部函數(shù)中用到供璧,就把這個臨時變量綁定給了內(nèi)部函數(shù)存崖,然后自己再結(jié)束。
閉包的作用:
可以隱藏內(nèi)部函數(shù)的工作細(xì)節(jié)睡毒,只給外部使用者提供一個可以執(zhí)行的內(nèi)部函數(shù)的引用来惧。避免了使用全局變量,保證了程序的封裝性保證了內(nèi)函數(shù)的安全性演顾,其他函數(shù)不能訪問
裝飾器
裝飾器就是用于拓展已有函數(shù)功能的一種函數(shù)供搀,這個函數(shù)的特殊之處在于它的返回值也是一個函數(shù),實(shí)際上就是利用閉包語法實(shí)現(xiàn)的钠至。
裝飾器的作用
在不用更改原函數(shù)的代碼前提下給函數(shù)增加新的功能葛虐。
嗯嗯嗯嗯……然后,就閱讀了這么一堆:
詳解Python的裝飾器
理解 Python 裝飾器
python 裝飾器 一篇就能講清楚
https://lotabout.me/2017/Python-Decorator/
巴拉巴拉巴拉大概意思就是棉钧,這個很重要屿脐,需要多理解~
我閱讀了這么多文章的唯一的好處(感慨)就是,就算沒理解宪卿,概念和基本使用也強(qiáng)行記住了
但是理解沒摄悯?
——不知道,
@⒉丁I菅薄!4位妗瘪阁!最后最后,只想安利:
最好的理解辦法就是邮偎,先寫一個簡單的例子管跺,然后,跟著調(diào)試器斷點(diǎn)一步一步走禾进,
然后多寫點(diǎn)其他的情況豁跑,斷點(diǎn)走走就知道其中的運(yùn)行機(jī)制了
最后跟著概念也就理解了
def decorator(fun):
print('I am in decorator!')
print('will do fun')
fun()
print('goodbye fun')
def inner():
print('I am come in inner')
fun()
print('inner will say goodbye')
#測試是否運(yùn)行test2
def test2():
print('I am in test2')
fun()
return inner
print('test')
@decorator
def outfun():
print('I am in outfun')
print('will do outfun')
outfun()
print('\nIt will do decorator')
decorator(outfun)
輸出:
test
I am in decorator!
will do fun
I am in outfun
goodbye fun
will do outfun
I am come in inner
I am in outfun
inner will say goodbye
It will do decorator
I am in decorator!
will do fun
I am come in inner
I am in outfun
inner will say goodbye
goodbye fun
當(dāng)然,關(guān)于裝飾器還有很多知識點(diǎn)泻云,遠(yuǎn)不止上面的測試程序這一點(diǎn)艇拍,詳細(xì)了解還是可以看看上面的鏈接文章的。
如宠纯,
對有參函數(shù)進(jìn)行裝飾
對帶返回值的函數(shù)進(jìn)行裝飾
帶參數(shù)的裝飾器
類裝飾器
通用裝飾器
內(nèi)置的裝飾器設(shè)置
……
下面的文字轉(zhuǎn)載:python裝飾器簡介
對有參函數(shù)進(jìn)行裝飾
在使用中卸夕,有的函數(shù)可能會帶有參數(shù),那么這種如何處理呢婆瓜?
代碼優(yōu)先:
def w_say(fun):
"""
如果原函數(shù)有參數(shù)快集,那閉包函數(shù)必須保持參數(shù)個數(shù)一致贡羔,并且將參數(shù)傳遞給原方法
"""
def inner(name):
"""
如果被裝飾的函數(shù)有行參,那么閉包函數(shù)必須有參數(shù)
:param name:
:return:
"""
print('say inner called')
fun(name)
return inner
@w_say
def hello(name):
print('hello ' + name)
hello('wangcai')
輸出為:
say inner called
hello wangcai
此時个初,也許你就會問了乖寒,那是一個參數(shù)的,如果多個或者不定長參數(shù)呢院溺,該如何處理呢宵统?看看下面的代碼你就秒懂了。
def w_add(func):
def inner(*args, **kwargs):
print('add inner called')
func(*args, **kwargs)
return inner
@w_add
def add(a, b):
print('%d + %d = %d' % (a, b, a + b))
@w_add
def add2(a, b, c):
print('%d + %d + %d = %d' % (a, b, c, a + b + c))
add(2, 4)
add2(2, 4, 6)
輸出結(jié)果為:
add inner called
2 + 4 = 6
add inner called
2 + 4 + 6 = 12
對帶返回值的函數(shù)進(jìn)行裝飾
下面對有返回值的函數(shù)進(jìn)行裝飾覆获,按照之前的寫法马澈,代碼是這樣的
def w_test(func):
def inner():
print('w_test inner called start')
func()
print('w_test inner called end')
return inner
@w_test
def test():
print('this is test fun')
return 'hello'
ret = test()
print('ret value is %s' % ret)
輸出結(jié)果為
w_test inner called start
this is test fun
w_test inner called end
ret value is None
可以發(fā)現(xiàn),此時弄息,并沒有輸出test函數(shù)的‘hello’,而是None痊班,那是為什么呢,可以發(fā)現(xiàn)摹量,在inner函數(shù)中對test進(jìn)行了調(diào)用涤伐,但是沒有接受不了返回值,也沒有進(jìn)行返回缨称,那么默認(rèn)就是None了凝果,知道了原因,那么來修改一下代碼:
def w_test(func):
def inner():
print('w_test inner called start')
str = func()
print('w_test inner called end')
return str
return inner
@w_test
def test():
print('this is test fun')
return 'hello'
ret = test()
print('ret value is %s' % ret)
結(jié)果輸出為:
w_test inner called start
this is test fun
w_test inner called end
ret value is hello
帶參數(shù)的裝飾器
介紹了對帶參數(shù)的函數(shù)和有返回值的函數(shù)進(jìn)行裝飾睦尽,那么有沒有帶參數(shù)的裝飾器呢器净,如果有的話,又有什么用呢当凡?
答案肯定是有的山害,接下來通過代碼來看一下吧。
def func_args(pre='xiaoqiang'):
def w_test_log(func):
def inner():
print('...記錄日志...visitor is %s' % pre)
func()
return inner
return w_test_log
# 帶有參數(shù)的裝飾器能夠起到在運(yùn)行時沿量,有不同的功能
# 先執(zhí)行func_args('wangcai')浪慌,返回w_test_log函數(shù)的引用
# @w_test_log
# 使用@w_test_log對test_log進(jìn)行裝飾
@func_args('wangcai')
def test_log():
print('this is test log')
test_log()
輸出結(jié)果為:
...記錄日志...visitor is wangcai
this is test log
簡單理解,帶參數(shù)的裝飾器就是在原閉包的基礎(chǔ)上又加了一層閉包朴则,通過外層函數(shù)func_args的返回值w_test_log就看出來了权纤,具體執(zhí)行流程在注釋里已經(jīng)說明了。
好處就是可以在運(yùn)行時乌妒,針對不同的參數(shù)做不同的應(yīng)用功能處理汹想。
通用裝飾器
介紹了這么多,在實(shí)際應(yīng)用中芥被,如果針對沒個類別的函數(shù)都要寫一個裝飾器的話欧宜,估計(jì)就累死了,那么有沒有通用萬能裝飾器呢拴魄,答案肯定是有的,廢話不多說,直接上代碼匹中。
def w_test(func):
def inner(*args, **kwargs):
ret = func(*args, **kwargs)
return ret
return inner
@w_test
def test():
print('test called')
@w_test
def test1():
print('test1 called')
return 'python'
@w_test
def test2(a):
print('test2 called and value is %d ' % a)
test()
test1()
test2(9)
輸出為:
test called
test1 called
test2 called and value is 9
類裝飾器
裝飾器函數(shù)其實(shí)是一個接口約束夏漱,它必須接受一個callable對象作為參數(shù),然后返回一個callable對象顶捷。
當(dāng)創(chuàng)建一個對象后挂绰,直接去執(zhí)行這個對象,那么是會拋出異常的服赎,因?yàn)樗皇莄allable葵蒂,無法直接執(zhí)行,但進(jìn)行修改后重虑,就可以直接執(zhí)行調(diào)用了践付,如下
class Test(object):
def __call__(self, *args, **kwargs):
print('call called')
t = Test()
print(t())
輸出為:
call called
下面,引入正題缺厉,看一下如何用類裝飾函數(shù)永高。
class Test(object):
def __init__(self, func):
print('test init')
print('func name is %s ' % func.__name__)
self.__func = func
def __call__(self, *args, **kwargs):
print('裝飾器中的功能')
self.__func()
@Test
def test():
print('this is test func')
test()
輸出結(jié)果為:
test init
func name is test
裝飾器中的功能
this is test func
和之前的原理一樣,當(dāng)python解釋器執(zhí)行到到@Test時提针,會把當(dāng)前test函數(shù)作為參數(shù)傳入Test對象,調(diào)用init方法,同時將test函數(shù)指向創(chuàng)建的Test對象舱殿,那么在接下來執(zhí)行test()的時候僵娃,其實(shí)就是直接對創(chuàng)建的對象進(jìn)行調(diào)用,執(zhí)行其call方法嗜价。
關(guān)于裝飾器里的上古神器:
https://zhangchuzhao.site/2018/05/25/python-decorator/
- @property -> getter/setter方法
- @classmethod落萎、@staticmethod
- @functools.wraps
- Easter egg
另外,
關(guān)于迭代器和生成器可參考下面的優(yōu)質(zhì)文章
完全理解Python迭代對象炭剪、迭代器练链、生成器
Python迭代器與生成器