1. 程式人生 > 程式設計 >Python類中的裝飾器在當前類中的宣告與呼叫詳解

Python類中的裝飾器在當前類中的宣告與呼叫詳解

我的Python環境:3.7

在Python類裡宣告一個裝飾器,並在這個類裡呼叫這個裝飾器。

程式碼如下:

class Test():
  xx = False

  def __init__(self):
    pass

  def test(func):
    def wrapper(self,*args,**kwargs):
      print(self.xx)
      return func(self,**kwargs)
      
    return wrapper

  @test
  def test_a(self,a,b):
    print(f'ok,{a} {b}')

注意:

1. 其中裝飾器test是在類Test中宣告並在其方法test_a中呼叫

2. 裝飾器test內層wrapper函式的首引數是self

補充知識: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,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,new_attrs)
        return new_attrs

      def __new__(cls,name,attrs):
        new_attrs = cls.options(bases,attrs)
        return super(Meta,cls).__new__(cls,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()


執行結果如圖

Python類中的裝飾器在當前類中的宣告與呼叫詳解

一個更完整的示例

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",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 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",in inner
EXCEPTION captured at function seven
  return func(*args,line 115,in one
  w = 1 / 0
ZeroDivisionError: integer division or modulo by zero

Traceback (most recent call last):
 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 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 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",in five
  w = 1 / 0
ZeroDivisionError: integer division or modulo by zero

程序已結束,退出程式碼 1

以上這篇Python類中的裝飾器在當前類中的宣告與呼叫詳解就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。