Python - 裝飾器使用過程中的誤區
曾靈敏 — APRIL 27, 2015
##裝飾器基本概念
大家都知道裝飾器是一個很著名的設計模式,經常被用於AOP(面向切面程式設計)的場景,較為經典的有插入日誌,效能測試,事務處理,` Web許可權校驗 `, `Cache `等。
Python語言本身提供了裝飾器語法(@),典型的裝飾器實現如下:
```
@function_wrapper
def function():
pass
```
@實際上是python2.4才提出的語法糖,針對python2.4以前的版本有另一種等價的實現:
```
def function():
pass
function = function_wrapper(function)
```
##裝飾器的兩種實現
函式包裝器 - 經典實現
```
def function_wrapper(wrapped):
def _wrapper(*args, **kwargs):
return wrapped(*args, **kwargs)
return _wrapper
@function_wrapper
def function():
pass
```
類包裝器 - 易於理解
```
class function_wrapper(object):
def __init__(self, wrapped):
self.wrapped = wrapped
def __call__(self, *args, **kwargs):
return self.wrapped(*args, **kwargs)
@function_wrapper
def function():
pass
```
##函式(function)自省
當我們談到一個函式時,通常希望這個函式的屬性像其文件上描述的那樣,是被明確定義的,例如` __name__` 和` __doc__ ` 。
針對某個函式應用裝飾器時,這個函式的屬性就會發生變化,但這並不是我們所期望的。
```
def function_wrapper(wrapped):
def _wrapper(*args, **kwargs):
return wrapped(*args, **kwargs)
return _wrapper
@function_wrapper
def function():
pass
>>> print(function.__name__)
_wrapper
```
python標準庫提供了` functools.wraps()` ,來解決這個問題。
```
import functools
def function_wrapper(wrapped):
@functools.wraps(wrapped)
def _wrapper(*args, **kwargs):
return wrapped(*args, **kwargs)
return _wrapper
@function_wrapper
def function():
pass
>>> print(function.__name__)
function
```
然而,當我們想要獲取被包裝函式的引數(` argument` )或原始碼(` source code `)時,同樣不能得到我們想要的結果。
```
import inspect
def function_wrapper(wrapped): ...
@function_wrapper
def function(arg1, arg2): pass
>>> print(inspect.getargspec(function))
ArgSpec(args=[], varargs='args', keywords='kwargs', defaults=None)
>>> print(inspect.getsource(function))
@functools.wraps(wrapped)
def _wrapper(*args, **kwargs):
return wrapped(*args, **kwargs)
```
##包裝類方法(@classmethod)
當包裝器(` @function_wrapper` )被應用於` @classmethod `時,將會丟擲如下異常:
```
class Class(object):
@function_wrapper
@classmethod
def cmethod(cls):
pass
Traceback (most recent call last):
File "
File "
File "
File ".../functools.py", line 33, in update_wrapper
setattr(wrapper, attr, getattr(wrapped, attr))
AttributeError: 'classmethod' object has no attribute '__module__'
```
因為` @classmethod` 在實現時,缺少` functools.update_wrapper` 需要的某些屬性。這是` functools.update_wrapper` 在python2中的bug,3.2版本已被修復,參考。
然而,在python3下執行,另一個問題出現了:
```
class Class(object):
@function_wrapper
@classmethod
def cmethod(cls):
pass
>>> Class.cmethod()
Traceback (most recent call last):
File "classmethod.py", line 15, in
Class.cmethod()
File "classmethod.py", line 6, in _wrapper
return wrapped(*args, **kwargs)
TypeError: 'classmethod' object is not callable
```
這是因為包裝器認定被包裝的函式(` @classmethod` )是可以直接被呼叫的,但事實並不一定是這樣的。被包裝的函式實際上可能是描述符(` descriptor` ),意味著為了使其可呼叫,該函式(描述符)必須被正確地繫結到某個例項上。關於描述符的定義,可以參考。
##總結 - 簡單並不意味著正確
儘管大家實現裝飾器所用的方法通常都很簡單,但這並不意味著它們一定是正確的並且始終能正常工作。
如同上面我們所看到的,` functools.wraps()` 可以幫我們解決` __name__` 和` __doc__` 的問題,但對於獲取函式的引數(argument)或原始碼( `source code` )則束手無策。
以上問題,[wrapt]()都可以幫忙解決,詳細用法可參考其官方文件:
------------
**本文作者系[OneAPM]()工程師曾靈敏 ,想閱讀更多好的[技術文章](),請訪問OneAPM官方技術部落格。**
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/30238867/viewspace-1630441/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Python 裝飾器裝飾類中的方法Python
- Python中的裝飾器Python
- python中的裝飾器的使用實戰Python
- python中裝飾器的原理Python
- 理解python中的裝飾器Python
- python中的裝飾器介紹Python
- Python 工匠:使用裝飾器的技巧Python
- python類裝飾器的使用注意Python
- python裝飾器2:類裝飾器Python
- Python 中的閉包與裝飾器Python
- 談一談Python中的裝飾器Python
- 深入理解Python中的裝飾器Python
- python的裝飾器Python
- Python裝飾器探究——裝飾器引數Python
- python裝飾器和描述器的使用總結Python
- Python 裝飾器Python
- Python裝飾器Python
- 裝飾器 pythonPython
- 介面測試使用Python裝飾器Python
- APP推廣過程中的10大誤區APP
- Javascript 中的裝飾器JavaScript
- Python 裝飾器的理解Python
- python裝飾器的集中使用姿勢Python
- 使用 TypeScript 裝飾器裝飾你的程式碼TypeScript
- JavaScript裝飾器的使用JavaScript
- Python的黑魔法@property裝飾器的使用技巧Python
- Python中裝飾器語法詳解Python
- Python中備忘功能和裝飾器Python
- python的裝飾器@的用法Python
- 在使用Kafka過程中遇到的錯誤Kafka
- Python中裝飾器的基本概念和用法Python
- 怎麼樣去理解 Python 中的裝飾器Python
- SEO優化過程中容易發生的誤區優化
- Python裝飾器模式Python模式
- 1.5.3 Python裝飾器Python
- Python 裝飾器(一)Python
- Python 裝飾器原理Python
- JavaScript中的裝飾器--DecoratorJavaScript