使用Python的元類實現AOP監控類方法調用過程

引言

Python的元類(metaclass)功能強大,能夠實現AOP(Aspect-Oriented Programming,面向切面編程)的類似功能,本文以實現監控類方法執行所耗時間爲例,參照本文,可以實現記錄類方法調用參數和執行結果到日誌文件中,統一增加類的方法或屬性等看似“不可能”的事情而不用對其它類做修改,達到“高內聚,低耦合”的目的。


什麼是元類(metaclass)

簡而言之,元類就是類的類(The class of a class)。有點拗口和抽象啊,呵呵,不過該定義非常恰當啊。定義類會創建一個類名稱,一個類的字典,一個基類列表。元類就是負責利用這三個參數和創建類。大部分面向對象編程(OOP)的語言提供一個缺省實現,而Python的特殊之處在於,我們可以創建定製的元類。大部分Python程序員都不會用到元類這一工具,但是當我們的需求出現時,元類能夠提供一個強大和優雅的解決方案。它已經使用在記錄屬性訪問,增加線程安全,跟蹤對象創建,實現單例(Singleton)和許多其他任務。


定製類的創建原理

缺省情況下,新型的類構造使用 type(), 一個類的定義被讀入一個不同的名字空間,類名稱的值綁定到 type(name,bases,dict)的結果上。當類的定義被讀入時,如果 __metaclass__定義了,賦值給它的可調用對象將取代type()而被調用,這將允許所寫的類或函數能夠監控或者修改類的創建過程:

1.在類被創建之前修改類的字典。

2.返回另外一個類的實例--本質上扮演了工廠函數的角色。

這些步驟必須在元類的__new__()方法中完成, 然後 type.__new__() 將會通過該方法被調用,從而創建一個具有不同特性的類。

決定使用哪個合適的元類,依據以下優先規則:

1. 如果 dict['__metaclass__'] 存在,就使用它作爲元類。

2.否則,如果存在有至少一個基類,那麼將會使用基類的元類。

3.否則,如果存在一個全局變量 __metaclass__,那麼將使用這個全局變量作爲元類。

4.否則,將會使用舊形式的傳統的元類(types.ClassType)。


元類使用舉例--修改類的屬性

下面的示例,使用元類增加一個叫“foo”的類屬性,我們先看代碼和運行結果:

#!/usr/bin/env python
#-*- coding: utf-8 -*-
#@author  : Thomas Hu
#@date    : 2015-04-13
#@version : 1.0

class MyMetaClass(type):
    def __new__(mcs, name, bases, dict):
        dict['foo'] = 'foo in MyMetaClass'
        return type.__new__(mcs, name, bases, dict)
    
class MyClass(object):
    __metaclass__ = MyMetaClass

if __name__ == "__main__":
    myobj =  MyClass()
    print myobj.foo
    print myboj.bar
輸出結果:

>>> 
foo in MyMetaClass

Traceback (most recent call last):
  File "D:\temp\metaclass\simple.py", line 18, in <module>
    print myboj.bar
NameError: name 'myboj' is not defined
>>> 
由於在元類“MyMetaClass”中,我們在類字典中增加了 “foo”屬性,所以調用 myobj.foo 不會出錯,而且打印出值;但對於 myobj.bar,由於 “bar”屬性並沒有在元類"MyMetaClass"中添加,也沒有在 "MyClass"類中定義,所以出錯。

注:元類會覆蓋原類定義的屬性。例如,如果在 MyClass中增加一個屬性,設置爲 foo="foo in MyClass",輸出的 myobj.foo的值仍然爲“foo in MyMetaClass"。

元類使用舉例--監控類方法調用

下面的示例代碼,展示如何結合包裝函數特性,通過元類監控類中所以函數類型的執行所耗費的時間。如下所示:
#!/usr/bin/env python
#-*- coding: utf-8 -*-
#@author  : Thomas Hu
#@date    : 2015-04-13
#@version : 1.0
import types
import time
import functools

def timefunc(func):
    '''Calculate the execution time of func.'''
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print("Function \"%s\" execute cost %d seconds."%(func.__name__, end - start))
        return result
    return wrapper


class MonitorMetaClass(type):
    def __new__(mcs, name, bases, attr_dict):
        for k, v in attr_dict.items():
            # If the attribute is function type, use the wrapper function instead
            if isinstance(v, types.FunctionType):
                attr_dict[k] = timefunc(v)
        return type.__new__(mcs, name, bases, attr_dict)
    

class TestClass(object):
    __metaclass__ = MonitorMetaClass
    def count_range(self, n):
        count = 0
        for i in range(n):
            count += i
            self._sleep()
        return count

    def _sleep(self, n=1):
        time.sleep(n)

if __name__ == "__main__":
    obj =  TestClass()
    value = obj.count_range(5)
    print("value=%d"%(value))

執行結果如下:
>>> 
Function "_sleep" execute cost 1 seconds.
Function "_sleep" execute cost 1 seconds.
Function "_sleep" execute cost 1 seconds.
Function "_sleep" execute cost 1 seconds.
Function "_sleep" execute cost 1 seconds.
Function "count_range" execute cost 5 seconds.
value=10
>>> 

元類使用舉例--實現singleton模式

利用元類,可以實現類的單例模式(singleton),操作起來十分簡單,如下所示:
#!/usr/bin/env python
#-*- coding: utf-8 -*-
#@author  : Thomas Hu
#@date    : 2015-04-13
#@version : 1.0

class SingletonMetaClass(type):
    def __init__(mcs, name, bases, attr_dict):
        type.__init__(mcs, name, bases, attr_dict)
        mcs._instance = None

    def __call__(mcs, *args, **kwargs):
        if mcs._instance is None:
            mcs._instance = type.__call__(mcs, *args, **kwargs)
        return mcs._instance
   

class TestClass(object):
    __metaclass__ = SingletonMetaClass
    name = "test class"


if __name__ == "__main__":
    obj1 =  TestClass()
    obj2 =  TestClass()
    print("obj1 id=%s, name=%s"%(id(obj1), obj1.name))
    print("obj2 id=%s, name=%s"%(id(obj2), obj2.name))
    obj1.name = "name changed"
    print("obj1 id=%s, name=%s"%(id(obj1), obj1.name))
    print("obj2 id=%s, name=%s"%(id(obj2), obj2.name))
輸出結果如下:
>>> 
obj1 id=46617392, name=test class
obj2 id=46617392, name=test class
obj1 id=46617392, name=name changed
obj2 id=46617392, name=name changed
>>> 
由此可見,以上代碼確實實現了單例模式,obj1和obj2的id值都相同,修改其中任意一個對象的屬性,另外一個對象的屬性也跟着變化(id都相同了,就是同一個對象了)。

小結

我們簡單的列舉了元類的實現和使用,但是一般情況下應用程序很少使用它,大多用在框架實現,測試工具實現,代碼跟蹤等方面。元類的潛在用途十分廣泛,比如日誌記錄、接口檢查(如抽象類的實現)、自動委託、自動屬性創建、代理、框架以及自動資源鎖和同步等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章