一、 裝飾函數
(1)概念:
裝飾器本質上是一個 Python 函數或類,它可以讓其他函數或類在不需要做任何代碼修改的前提下 增加額外功能 ,裝飾器的返回值也是一個 函數/類對象 。它經常用于有切面需求的場景,比如:插入日志、性能測試、事務處理、緩存、權限校驗等場景,裝飾器是解決這類問題的絕佳設計。有了裝飾器,我們就可以抽離出大量與函數功能本身無關的雷同代碼到裝飾器中并繼續重用。概括的講,裝飾器的作用就是 為已經存在的對象添加額外的功能 。
使用方法:
- 先定義一個裝飾器(帽子)
- 再定義你的業務函數或者類(人)
- 最后把這裝飾器(帽子)扣在這個函數(人)頭上
(2)實例:
- 日志打印器
# 這是裝飾器函數,參數 func 是被裝飾的函數
def
logger
(
func
)
:
def
wrapper
(
*
args
,
**
kw
)
:
print
(
'主人,我準備開始執行:{} 函數了:'
.
format
(
func
.
__name__
)
)
# 真正執行的是這行。
func
(
*
args
,
**
kw
)
print
(
'主人,我執行完啦。'
)
return
wrapper
@logger
# =》 add = logger(add)
def
add
(
x
,
y
)
:
print
(
'{} + {} = {}'
.
format
(
x
,
y
,
x
+
y
)
)
# 這是裝飾函數
def
timer
(
func
)
:
def
wrapper
(
*
args
,
**
kw
)
:
t1
=
time
.
time
(
)
# 這是函數真正執行的地方
func
(
*
args
,
**
kw
)
t2
=
time
.
time
(
)
# 計算下時長
cost_time
=
t2
-
t1
print
(
"花費時間:{}秒"
.
format
(
cost_time
)
)
return
wrapper
import
time
@timer
def
want_sleep
(
sleep_time
)
:
time
.
sleep
(
sleep_time
)
want_sleep
(
10
)
#花費時間:10.000298261642456秒
- 帶參數的函數裝飾器
def
say_hello
(
contry
)
:
def
wrapper
(
func
)
:
def
deco
(
*
args
,
**
kwargs
)
:
if
contry
==
"china"
:
print
(
"你好!"
)
elif
contry
==
"america"
:
print
(
'hello.'
)
else
:
return
# 真正執行函數的地方
func
(
*
args
,
**
kwargs
)
return
deco
return
wrapper
# 小明,中國人
@say_hello
(
"china"
)
def
xiaoming
(
)
:
pass
# jack,美國人
@say_hello
(
"america"
)
def
jack
(
)
:
pass
4. 不帶參數的類裝飾器
基于類裝飾器的實現,必須實現
call
和 __init__兩個內置函數。
init
:接收被裝飾函數
call
:實現裝飾邏輯。
class
logger
(
object
)
:
def
__init__
(
self
,
func
)
:
self
.
func
=
func
def
__call__
(
self
,
*
args
,
**
kwargs
)
:
print
(
"[INFO]: the function {func}() is running..."
\
.
format
(
func
=
self
.
func
.
__name__
)
)
return
self
.
func
(
*
args
,
**
kwargs
)
@logger
def
say
(
something
)
:
print
(
"say {}!"
.
format
(
something
)
)
say
(
"hello"
)
#[INFO]: the function say() is running...
#say hello!
-
帶參數的類裝飾器
上面不帶參數的例子,你發現沒有,只能打印INFO級別的日志,正常情況下,我們還需要打印DEBUG WARNING等級別的日志。這就需要給類裝飾器傳入參數,給這個函數指定級別了。
帶參數和不帶參數的類裝飾器有很大的不同。
init
:不再接收被裝飾函數,而是接收傳入參數。
call
:接收被裝飾函數,實現裝飾邏輯。
class
logger
(
object
)
:
def
__init__
(
self
,
level
=
'INFO'
)
:
self
.
level
=
level
def
__call__
(
self
,
func
)
:
# 接受函數
def
wrapper
(
*
args
,
**
kwargs
)
:
print
(
"[{level}]: the function {func}() is running..."
\
.
format
(
level
=
self
.
level
,
func
=
func
.
__name__
)
)
func
(
*
args
,
**
kwargs
)
return
wrapper
#返回函數
@logger
(
level
=
'WARNING'
)
def
say
(
something
)
:
print
(
"say {}!"
.
format
(
something
)
)
say
(
"hello"
)
#[WARNING]: the function say() is running...
#say hello!
- 內置裝飾器:property
- 如果 decorator本身需要傳入參數 ,那就需要編寫一個返回decorator的高階函數。(即再嵌套一個decorator函數):
def
log
(
text
)
:
def
decorator
(
func
)
:
@functools
.
wraps
(
func
)
#把原始函數的__name__等屬性復制到wrapper()函數中
def
wrapper
(
*
args
,
**
kw
)
:
print
(
'%s %s():'
%
(
text
,
func
.
__name__
)
)
return
func
(
*
args
,
**
kw
)
return
wrapper
return
decorator
@log
(
'execute'
)
#now = log('execute')(now)
def
now
(
)
:
print
(
'2015-3-25'
)
now
(
)
#execute now():
#2015-3-25
首先執行log(‘execute’),返回的是decorator函數,再調用返回的函數,參數是now函數,返回值最終是wrapper函數。
二、閉包
(1)概念:
在一個內部函數中,對外部作用域的變量進行引用,(并且一般外部函數的返回值為內部函數),那么內部函數就被認為是閉包。
維基百科上的解釋是:
在計算機科學中,閉包(Closure)是詞法閉包(Lexical Closure)的簡稱, 是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在, 即使已經離開了創造它的環境也不例外。 所以,有另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。
add訪問了外部函數start的變量,并且函數返回值為add函數(python可以
返回函數
)
閉包,顧名思義,就是一個封閉的包裹,里面包裹著
自由變量
,就像在類里面定義的屬性值一樣,自由變量的可見范圍隨同包裹,哪里可以訪問到這個包裹,哪里就可以訪問到這個自由變量。
再通過Python的語言介紹一下,一個閉包就是你調用了一個函數A,這個函數A返回了一個函數B給你。這個返回的函數B就叫做閉包。你在調用函數A的時候傳遞的參數就是自由變量(當函數A的生命周期結束之后,自由變量依然存在,因為它被閉包引用了,所以不會被回收。)。
(2)常見問題
-
閉包無法修改 外部函數 的 局部變量 (即add函數無法修改start函數定義的變量)
-
閉包使得 局部變量 在 函數外 被訪問成為可能
-
閉包避免了使用全局變量
-
閉包允許將函數與其所操作的某些數據(環境)關連起來。
-
裝飾器就是一種的閉包的應用,只不過其傳遞的是 函數 。
-
閉包的最大特點是可以將 父函數的變量與內部函數綁定 ,并返回綁定變量后的函數(也即閉包)。(類似類)
-
python循環中不包含域的概念。
loop在python中是沒有域的概念的,flist在向列表中添加func的時候,并沒有保存i的值,而是當執行f(2)的時候才去取,這時候循環已經結束,i的值是2,所以結果都是4。
解決辦法:
在func外面再定義一個makefun函數,func形成閉包。
參考:
https://foofish.net/python-closure.html
https://zhuanlan.zhihu.com/p/22229197
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
