python-類內函數的全局裝飾器

有時,比如寫RF的測試庫的時候,很多方法都寫在一個類裏。我們又可能需要一個通用的裝飾器,比如,要給某個底層類的方法打樁,查看入參和出參,用以理解業務;或者要hold住所有的執行錯誤,打印堆棧又不想程序退出或用例直接失敗
比如捕捉錯誤的裝飾器

import traceback
from functools import wraps


def trier(soft=False):
    '''
    :param bool soft: 爲True時,打印報錯堆棧並忽略異常。默認False,打印報錯堆棧並拋出異常
    :return:
    如果要給類方法、靜態方法裝飾,則該裝飾器必須處於比@staticmethod裝飾器更內一層才行
    '''
    def realTrier(func):
        '''
        :param function func:
        :return:
        '''
        @wraps(func)  # 保留__name__ __doc__ __module__
        def innerfunc(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except Exception, e:
                try:
                    print(traceback.format_exc())
                except:
                    print e
                if not soft:
                    raise
        return innerfunc
    return realTrier

或者參數跟蹤的裝飾器

def tracer(func):
	def infunc(*args, **kwargs):
		print func.__name__, args, kwargs
		res=infunc(*args, **kwargs)
		print func.__name__, res
		return res

這類裝飾器經常會給類裏的每個函數都使用
每次都裝飾的話,也挺麻煩

python裏可以給類寫個裝飾器,所以可以輸入一個類,返回一個新類,這個新類擁有原來類裏的所有方法,但所有方法都被裝飾
使用元類,可以做到這一點。
目前可以批量裝飾普通方法、靜態方法、類方法、屬性,暫不支持__init__和__del__之類的特殊方法,以免出現意外的問題。
目前類B使用了全局裝飾器,假如類B繼承自類A,類C繼承自類B
則類B、類C內的所有方法都被全局裝飾(全局裝飾可以被繼承)
且類B繼承自類A的所有方法也會被全局裝飾
但這種裝飾不會影響到類A,調用類A下的方法時,所有方法都不被裝飾

經過多次嘗試,最後的實現代碼如下

# clswrapper.py
def skipper(func):
    '''
    :param function func:
    :return:
    '''
    func.__funskip__=True
    return func


def classWrapper(commonDecoratorFunc):
    def innerMata(inClass):
        def collect_attrib(key, value, new_attrs):
            if hasattr(value, '__funskip__'):
                new_attrs[key] = value
                return
            if hasattr(value, '__func__') or isinstance(value, types.FunctionType):
                if isinstance(value, staticmethod):
                    new_attrs[key] = staticmethod(commonDecoratorFunc(value.__func__))
                    return
                elif isinstance(value, classmethod):
                    new_attrs[key] = classmethod(commonDecoratorFunc(value.__func__))
                    return
                elif not key.startswith('__'):
                    new_attrs[key] = commonDecoratorFunc(value)
                    return
            else:
                if isinstance(value, property):
                    # 當對property類進行重組的時候,我們強制裝飾了property類的fget fset和fdel方法。但是,不是每個propery都有這三個方法,有些是None,強制裝飾會報錯,所以我們這裏要考慮提前返回None
                    propertyWrapper = property(fget=commonDecoratorFunc(value.fget) if value.fget else None,
                                               fset=commonDecoratorFunc(value.fset) if value.fset else None,
                                               fdel=commonDecoratorFunc(value.fdel) if value.fdel else None,
                                               doc=value.__doc__)
                    new_attrs[key] = propertyWrapper
                    return
            new_attrs[key] = value

        class Meta(type):
            @classmethod
            def options(cls, bases, attrs):
                new_attrs = {}
                for key, value in attrs.items():
                    collect_attrib(key, value, new_attrs)
                for base in bases:
                    for mbase in base.mro():
                        for key, value in mbase.__dict__.items():
                            if key not in new_attrs:
                                collect_attrib(key, value, new_attrs)
                return new_attrs

            def __new__(cls, name, bases, attrs):
                new_attrs = cls.options(bases, attrs)
                return super(Meta, cls).__new__(cls, name, bases, new_attrs)
        return six.add_metaclass(Meta)(inClass)
    return innerMata

其中,skipper提供了一個後門,被skipper裝飾的函數會跳過全局裝飾器
使用方法如下

@classWrapper(trier(soft=True))
class Tree(object):
    @skipper
    def div(self):
        return 1/0
    
    def divsafe(self):
        return 1/0

t=Tree()
print t.divsafe()
print t.div()

執行結果如圖
在這裏插入圖片描述
一個更完整的示例

from clswrapper那個文件 import skipper, classWrapper
import traceback
from functools import wraps

'''爲簡潔起見,這次我們用的是不帶參數的trier裝飾器'''
def trier(func):
    @wraps(func)
    def inner(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except:
            print("EXCEPTION captured at function %s" % func.__name__, file=sys.stderr)
            print(traceback.format_exc().decode("gbk"))
            raise
    return inner

if __name__=="__main__":
    import time
    class mobj(object):
        def five(self):
            w = 1 / 0


    class obj(mobj):

        def __init__(self):
            # print "obj.__init__"
            return

        @classmethod
        def one(self):
            w = 1 / 0
            print('obj.one')


    @classWrapper(trier)  # 或者用@classWrapper(argTrier(True))替換,則可以不拋出異常
    class obj1(obj):
        aa = 1

        def __init__(self):
            super(obj1, self).__init__()
            self.var = 1

        @classmethod
        def three(cls):
            w = 1 / 0
            print('obj1.three')

        @staticmethod
        def four():
            w = 1 / 0
            print('obj1.four')

        def two(self):
            w = 1 / 0
            print(self.pro)
            print('obj1.two')

        @property
        def pro(self):
            return self.var

        @pro.setter
        def pro(self, value):
            self.var = value / 0

        @skipper
        def eight(self):
            w=1/0
            return w

    class outerobj(obj1):
        def seven(self):
            return 1/0

    b = obj1()
    a = obj1

    print(b.var)

    try:
        b.two()
    except:
        pass
    try:
        a.three()
    except:
        pass
    try:
        a.four()
    except:
        pass
    try:
        a.one()
    except:
        pass
    try:
        b.five()
    except:
        pass

    try:
        b.pro = 3
    except:
        pass
    print(b.pro)

    print(a.aa)

    c=outerobj()
    try:
        c.five()
    except:
        pass

    try:
        c.seven()
    except:
        pass

    try:
        c.eight()
    except:
        print("c.eight被跳過,所以沒有被裏層捕獲,纔會不打堆棧直接走到這裏")
  
    print("最後這個會真正觸發異常,因爲mobj實例並沒有被裝飾過")
    m=mobj()
    time.sleep(1)
    m.five()

它展示了這個強大裝飾器能處理的各種情況,執行結果應該如下

1
EXCEPTION captured at function two
EXCEPTION captured at function three
Traceback (most recent call last):
EXCEPTION captured at function four
  File "E:/pydev/異常處理裝飾器.py", line 37, in inner
EXCEPTION captured at function one
    return func(*args, **kwargs)
EXCEPTION captured at function five
  File "E:/pydev/異常處理裝飾器.py", line 138, in two
    w = 1 / 0
ZeroDivisionError: integer division or modulo by zero

Traceback (most recent call last):
  File "E:/pydev/異常處理裝飾器.py", line 37, in inner
    return func(*args, **kwargs)
  File "E:/pydev/異常處理裝飾器.py", line 129, in three
    w = 1 / 0
ZeroDivisionError: integer division or modulo by zero

Traceback (most recent call last):
  File "E:/pydev/異常處理裝飾器.py", line 37, in inner
    return func(*args, **kwargs)
  File "E:/pydev/異常處理裝飾器.py", line 134, in four
    w = 1 / 0
EXCEPTION captured at function pro
ZeroDivisionError: integer division or modulo by zero

EXCEPTION captured at function five
Traceback (most recent call last):
EXCEPTION captured at function five
  File "E:/pydev/異常處理裝飾器.py", line 37, in inner
EXCEPTION captured at function seven
    return func(*args, **kwargs)
  File "E:/pydev/異常處理裝飾器.py", line 115, in one
    w = 1 / 0
ZeroDivisionError: integer division or modulo by zero

Traceback (most recent call last):
  File "E:/pydev/異常處理裝飾器.py", line 37, in inner
    return func(*args, **kwargs)
  File "E:/pydev/異常處理裝飾器.py", line 104, in five
    w = 1 / 0
ZeroDivisionError: integer division or modulo by zero

Traceback (most recent call last):
  File "E:/pydev/異常處理裝飾器.py", line 37, in inner
    return func(*args, **kwargs)
  File "E:/pydev/異常處理裝飾器.py", line 148, in pro
    self.var = value / 0
ZeroDivisionError: integer division or modulo by zero

1
1
Traceback (most recent call last):
  File "E:/pydev/異常處理裝飾器.py", line 37, in inner
    return func(*args, **kwargs)
  File "E:/pydev/異常處理裝飾器.py", line 104, in five
    w = 1 / 0
ZeroDivisionError: integer division or modulo by zero

Traceback (most recent call last):
  File "E:/pydev/異常處理裝飾器.py", line 37, in inner
    return func(*args, **kwargs)
  File "E:/pydev/異常處理裝飾器.py", line 37, in inner
    return func(*args, **kwargs)
  File "E:/pydev/異常處理裝飾器.py", line 104, in five
    w = 1 / 0
ZeroDivisionError: integer division or modulo by zero

Traceback (most recent call last):
  File "E:/pydev/異常處理裝飾器.py", line 37, in inner
    return func(*args, **kwargs)
  File "E:/pydev/異常處理裝飾器.py", line 157, in seven
    return 1/0
ZeroDivisionError: integer division or modulo by zero

c.eight被跳過,所以沒有被裏層捕獲,纔會不打堆棧直接走到這裏
最後這個會真正觸發異常,因爲mobj實例並沒有被裝飾過
Traceback (most recent call last):
  File "E:/pydev/�쳣����װ����.py", line 212, in <module>
    m.five()
  File "E:/pydev/�쳣����װ����.py", line 104, in five
    w = 1 / 0
ZeroDivisionError: integer division or modulo by zero

進程已結束,退出代碼 1
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章