這篇文章主要深入學習了python裝飾器的相關資料,什麼是裝飾器?裝飾器遵循的原則等,具有一定的參考價值,有興趣的小伙伴們可以參考一下
什麼是裝飾器
在我們的軟體產品升級時,常常需要為各個函數新增功能,而在我們的軟體產品中,相同的函數可能會被調用上百次,這種情況是很常見的,如果我們一個個的修改,那我們的碼農豈不要掛掉了(有人就說了,你笨呀,修改函數定義不就行了!同學,你醒醒吧,如果要新加的功能會修改參數,或者返回值呢?)。這時候,就是我們裝飾器大顯神通的時候了。裝飾器就可以實現,在不改變原函數的呼叫形式下(即函數的透明化處理),為函數新增功能的作用。如何實現,以及實現原理,下文會詳解。
裝飾者遵循的原則
裝飾器,顧名思義就是起裝飾的作用,既然是裝飾,那麼被裝飾的物件是啥樣就是啥樣,不能有絲毫改變。 在這裡,我們寫裝飾器就是必須掌握不能修改被修飾函數的原始碼這條鐵律。 如何遵循這條鐵律,我們還需還需做一些鋪墊,必須先了解三個概念,如下:
函數名稱即「變數」
在python中,函數名其實就像是c語言的函數指針,代表的是我們的函數位址,只有解釋器取得到這個位址,它才會去執行這塊記憶體的代碼。因此,本質上,函數名就和不同變數沒什麼區別,只不過函數名和普通變數所指涉的那塊記憶體的使用方式不同罷了,這些都是底層解釋器的機制所決定的,對於程式猿來說,都是透明的,所以,我們可以認為兩者是沒有差別的。
高階函數
什麼是高階函數其實很簡單,把握兩個原則就好:
#形式參數有函數名稱
傳回值有函數名稱
#只要滿足這兩個原則之一,就可以稱為是高階函數。翻回頭來看,這裡出現了我們上面說的函數名,仔細體會一下,我們在這裡不就是把其當成實參看待的嗎?
巢狀函數字
#什麼是巢狀函數其實也很簡單,要把握一個原則就好:
在一個函數的函數體中去定義另一個函數
在這裡需要強調的是,函數定義時是不會執行函數體的,就和定義變數是不會去讀取變數裡的內容一樣。這一點至關重要,對於我們理解裝飾器實作原理非常有幫助。
如何寫裝飾器
有了上文的鋪墊,在現在來詳解一下如何寫裝飾器,就好理解多了。
裝飾器本質
其實裝飾器本質上就是一個函數,它也有函數名,參數和傳回值。但在python中,我們用「@auth」來表示。
@auth # 其等价于:func = auth(func) def func(): print("func called")
這個範例就是python中如何修飾func函數的格式,當然我們還沒有實作我們的裝飾函數。我們要注意的是註解裡寫的內容,我們可以看出:
裝飾函數其實是高階函數(參數和回傳值都為函數名)。
「auth(func)」是在呼叫我們的裝飾器函數,也就是裝飾器函數的函數體會被執行,一定要記好這一點。
設計想法
#裝飾子即是函數,又有上述介紹的等價關係,那我們就可以這樣設計我們的裝飾器:
在我們裝飾器的函數體內去定義一個新的函數,在這個新定義的函數內去呼叫被修飾的函數,同時,在被修飾的函數的上下文去加入新功能。最後,利用裝飾器函數的回傳值來傳回我們新定義函數的函數名稱。
由此可以知道,「func = auth(func)」中的回傳值func表示的就是在裝飾器中新定義的函數的函數名稱。
前面做了大量的鋪墊,就是想在這裡揭示裝飾器的實作機制,其實沒什麼什麼的,很簡單:
装饰器机制改变了被修饰函数的函数名表示的地址数据。说白了就是,被修饰前,函数名代表的是A内存块;被修饰后,函数名代表的是B内存块;只不过,在执行B内存块时,会调用A内存块罢了。B内存块中的代码就是我们新加的功能。而这种机制的实现,使用了“高阶函数”和“嵌套函数”的机制。
最终的效果就是,但在调用被修饰过的函数时,其实调用的不是原来的内存块,而是修饰器新申请的内存块。
第一步:设计装饰器函数
装饰器函数定义跟普通函数定义没什么区别,关键是函数体怎么写的问题。这里,为了便于理解,先用无参数的装饰器函数说明。
#装饰器函数定义格式 def deco(func): '''函数体...''' return func
这里说的无参数,指的是没有除了“func”之外的参数
难点是函数体的编写,下面的示例先告诉你为什么要有第二步:
#使用语法糖@来装饰函数,相当于“myfunc = deco(myfunc)” def deco(func): print("before myfunc() called.") func() print("after myfunc() called.") return func @deco def myfunc(): print("myfunc() called.") myfunc() myfunc() #output: before myfunc() called. myfunc() called. after myfunc() called. myfunc() called. myfunc() called.
由输出结果可以看出,我们的装饰器并没有生效。别跟我说装饰器只生效了一次,那是大家忽略了“@deco”的等效机制。解释到“@deco”时,会解释成“myfunc = deco(myfunc)”。注意了,前面我提到了,这里其实在调用deco函数的,因此,deco的函数体会被执行。所以output的前三行并不是调用myfunc函数时产生的效果,那有怎能说装饰器生效了一次呢?第二步就是解决装饰器没生效的问题的。
第二步:包装被修饰函数
#基本格式 def deco(func): def _deco() #新增功能 #... #... func() #别修饰函数调用 return_deco
下面给出个示例:
#使用内嵌包装函数来确保每次新函数都被调用, #内嵌包装函数的形参和返回值与原函数相同,装饰函数返回内嵌包装函数对象 def deco(func): def _deco(): print("before myfunc() called.") func() print("after myfunc() called.") # 不需要返回func,实际上应返回原函数的返回值 return _deco @deco def myfunc(): print("myfunc() called.") return 'ok' myfunc() #output: before myfunc() called. myfunc() called. after myfunc() called.
第三步:被修饰函数参数和返回值透明化处理
当完成了第二步时,其实装饰器已经完成了主要部分,下面就是对被修饰函数的参数和返回值的处理。这样才能真正实现装饰器的铁律。话不多说,直接上代码:
#基本格式 def deco(func): def _deco(*args, **kwargs) #参数透明化 #新增功能 #... #... res = func(*args, **kwargs) #别修饰函数调用 return res #返回值透明化 return_deco
通过上面的分析知:
参数透明化:当我们在调用被装饰后的函数时,其实调用的时这里的_deco函数。那么,我们就给_deco函数加上可变参数,并把得到的可变参数传递给func函数不就可以了。
返回值透明化:和参数透明化同理,给_deco函数定义返回值,并返回func的返回值就可以了。
透明化处理就是这么简单!至此,我们的装饰器编写完成。给个示例吧:
#对带参数的函数进行装饰, #内嵌包装函数的形参和返回值与原函数相同,装饰函数返回内嵌包装函数对象 def deco(func): def _deco(*agrs, **kwagrs): print("before myfunc() called.") ret = func(*agrs, **kwagrs) print(" after myfunc() called. result: %s" % ret) return ret return _deco @deco def myfunc(a, b): print(" myfunc(%s,%s) called." % (a, b)) return a + b print("sum=",myfunc(1, 2)) print("sum=",myfunc(3, 4)) #output: before myfunc() called. myfunc(1,2) called. after myfunc() called. result: 3 sum= 3 before myfunc() called. myfunc(3,4) called. after myfunc() called. result: 7 sum= 7
装饰器进阶
带参数装饰器
装饰器即然也是函数,那么我们也可以给其传递参数。我这里说的是:“@auth(auth_type = 'type1')”这中形式哟。先上个代码吧:
#基本格式 def deco(deco_type) def _deco(func): def __deco(*args, **kwargs) #参数透明化 #新增功能 #... #... print("deco_type:",deco_type) #使用装饰器参数 res = func(*args, **kwargs) #别修饰函数调用 return res #返回值透明化 return __deco return_deco
说白了,就是在原来的装饰器的基础上再在最外层套一个deco函数,并用其来接收装饰器参数。由于是在最外层套了一个函数,那么这个函数的形参的作用范围就是函数体内部,所以里面的函数定义中随便用啦,就这么任性。
那怎么理解解释器的解析过程呢?在这里,只要我们明白一点就好,那就是: “@auth(auth_type = 'type1')”等价于“func = auth(auth_type = 'type1')(func)” 。解释器会先翻译“auth(auth_type = 'type1')”,再将其返回值(假设给了_func这个不存在的函数名)当作函数指针,这里的_func函数名代表的是_deco,然后再去执行“func = _func(func)”,而这个func函数名代表的其实就是__deco。
至此,就达到了通过装饰器来传参的目的。给个示例吧:
#示例7: 在示例4的基础上,让装饰器带参数, #和上一示例相比在外层多了一层包装。 #装饰函数名实际上应更有意义些 def deco(deco_type): def _deco(func): def __deco(*args, **kwagrs): print("before %s called [%s]." % (func.__name__, deco_type)) func(*args, **kwagrs) print(" after %s called [%s]." % (func.__name__, deco_type)) return __deco return _deco @deco("mymodule") def myfunc(): print(" myfunc() called.") @deco("module2") def myfunc2(): print(" myfunc2() called.") myfunc() myfunc2() #output: before myfunc called [mymodule]. myfunc() called. after myfunc called [mymodule]. before myfunc2 called [module2]. myfunc2() called. after myfunc2 called [module2].
多重装饰器修饰函数
如果说,我上面说的内容都理解了,那么这个东东,就太简单不过了。不就是把我们的是装饰器当中被修饰的函数,对它进行装饰吗?但我在这里还想说的是,我们换个角度看问题。我们的关注点放在原来的被修饰的函数上,就会发现,NB呀,我可以给它添加若干个功能撒。给个示例吧:
def deco(deco_type): def _deco(func): def __deco(*args, **kwagrs): print("before %s called [%s]." % (func.__name__, deco_type)) func(*args, **kwagrs) print(" after %s called [%s]." % (func.__name__, deco_type)) return __deco return _deco @deco("module1") @deco("mymodule") def myfunc(): print(" myfunc() called.") @deco("module2") def myfunc2(): print(" myfunc2() called.") myfunc() #output: before __deco called [module1]. before myfunc called [mymodule]. myfunc() called. after myfunc called [mymodule]. after __deco called [module1].
注意结果哟,@deco("module1"),来修饰的deco("mymdule")的,和我们想的是一样的,完美!
相关推荐:
#
以上是python裝飾器深入學習_python的詳細內容。更多資訊請關注PHP中文網其他相關文章!