Python裝飾器實例

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特性的文章。它是一個網絡上一些優秀文章的資源集合。

發佈了29 篇原創文章 · 獲贊 8 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章