Python裝飾器入門
如果還不知道Python裝飾器是什麼東西,可以閱讀這篇文章。它深入淺出地用實例介紹了裝飾器,並且還介紹內置裝飾器和functools包。另外,這篇文章對裝飾器支持參數傳遞有一些簡單的示例。也可以作爲一個入門的參考。
Flask裏使用裝飾器的幾個例子
setupmethod
需求
如果web應用程序從運行起處理過一個HTTP請求,這個時候再向Flask添加route規則,則Flask在調試模式下,可以檢查出一個APP的錯誤。爲什麼這是個錯誤呢?想像一下,如果一個web應用程序已經上線提供服務了,這個時候由於某些條件觸發,向Flask又添加了一個route規則,則說明在添加這個route規則之前,向這個route發送的請求是沒有接口可以處理的。
解決方案
Flask定義了setupmethod
裝飾器來確保所有的初始化函數必須在web應用程序開始提供服務之前被調用。如果在已經提供服務之後被調用,則報錯。
源碼分析
setupmethod
裝飾器源碼定義在app.py裏:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def setupmethod(f): """Wraps a method so that it performs a check in debug mode if the first request was already handled. """ def wrapper_func(self, *args, **kwargs): if self.debug and self._got_first_request: raise AssertionError('A setup function was called after the ' 'first request was handled. This usually indicates a bug ' 'in the application where a module was not imported ' 'and decorators or other functionality was called too late.\n' 'To fix this make sure to import all your view modules, ' 'database models and everything related at a central place ' 'before the application starts serving requests.') return f(self, *args, **kwargs) return update_wrapper(wrapper_func, f) |
- Line 6:這裏確保用
setupmethod
裝飾的函數在被實際調用之前,都會去檢查self._got_first_request
- Line 14:這裏的
f(self, *args, **kwargs)
確保setupmethod
裝飾器可以裝飾任何參數形式的函數 - Line 15: update_wrapper是functools包提供的一個函數。用來確保被裝飾的函數依然可以支持Python的反射機制
locked_cached_property
需求
有些屬性的計算比較昂貴,如果這個屬性又是非常經常被調用。那麼把這個屬性計算一次後,緩存起來,以確保下次訪問時直接訪問計算過的值。這個對性能的優化是比較有幫助的。
解決方案
Flask定義了locked_cached_property
裝飾器來實現上述的需求。同時還提供了鎖以保存併發線程訪問的安全性。
源碼分析
locked_cached_property
裝飾器源碼定義在helpers.py裏:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class locked_cached_property(object): """A decorator that converts a function into a lazy property. The function wrapped is called the first time to retrieve the result and then that calculated result is used the next time you access the value. Works like the one in Werkzeug but has a lock for thread safety. """ def __init__(self, func, name=None, doc=None): self.__name__ = name or func.__name__ self.__module__ = func.__module__ self.__doc__ = doc or func.__doc__ self.func = func self.lock = RLock() def __get__(self, obj, type=None): if obj is None: return self with self.lock: value = obj.__dict__.get(self.__name__, _missing) if value is _missing: value = self.func(obj) obj.__dict__[self.__name__] = value return value |
Line 1: 注意這裏使用類來實現裝飾器,而不是我們常見的函數。
Line 14: 這裏用RLock來實現併發線程訪問安全性。
Line 16: 這裏實現了__get__
,即locked_cached_property
是一個non-data
descriptor類。
我們看一下app.py裏的name
方法對這個裝飾器是怎麼使用的:
@locked_cached_property def name(self): ...
根據python decorator原理,上述代碼實際上相當於下面的python代碼:
1 2 3 |
def name(self): ... name = locked_cached_property(name) |
根據python descriptor協議,Line 3的代碼實際上就是定義了一個類的只讀屬性。即訪問app.name
時,實際上執行的是locked_cached_property.__get__
方法。
這樣就實現了app.name
屬性只計算一次,且併發訪問安全的需求。當然,實現這一需求的方案有很多種,Flask裏使用的這種實現方法優雅且pythonic。更重要的是,它使用了AOP(Aspect-Orient Program)編程思想,提高了代碼的可複用性。
專業術語
如果你對本文使用的很多專業術語感到困惑,可閱讀另外一篇介紹Python特性的文章。它是一個網絡上一些優秀文章的資源集合。