python-描述符(descriptor)

原文地址:Descriptor HowTo Guide

描述符定義

一般來說,描述符是一個具有“binding behavior”的對象屬性,它的屬性訪問被描述符協議里的方法重寫。這些方法是__get__(),__set__(),__delete__(),如果這三個方法中任意一個被某個對象實現,那么該對象就被成為描述符。

屬性訪問的默認行為是從對象的詞典中get、set或者delete屬性。例如,a.x則依次從a.__dict__['x']type(a).__dict__['x']type(a)的除元類外的基類的__dict__中查找。然后最終的查找結果是描述符的話,那么Python可能會重寫默認行為并調用描述符方法,這取決于定義了哪些描述符方法。Note that descriptors are only invoked for new style objects or classes (a class is new style if it inherits from object or type).

描述符是一個強大的通用協議,它們是屬性,方法,靜態方法,類方法和super()的機制。它們被用于整個Python本身,以實現版本2.2中引入的新風格類。描述符簡化了底層C代碼,為日常Python程序提供了一套靈活的新工具。

描述符協議

  • descr.__get__(self, obj, type=None) --> value

  • descr.__set__(self, obj, value) --> None

  • descr.__delete__(self, obj) --> None
    這是描述符協議的全部,定義其中的任意一個就會被視為描述符并且當作為屬性被查找時會改變其默認行為。
    如果一個對象定義了__get__()__set__()那么它被稱為“data descriptor”,只定義__get__()被稱為‘non-data descriptors’。
    如果一個實例的__dict__里有和‘data descriptor’同名的條目,那么描述符優先;如果實例的__dict__里有和‘no-data descriptors’同名的方法,那么字典條目優先。
    寫個例子試一下:

    class Desc(object):
        def __init__(self, x):
        self.x = x
    
    def __set__(self, obj, value):
       self.x = value
    
    def __get__(self, obj, type=None):
        print 'invoking __get__'
        return self.x
    
    class A(object):
        d = Desc(12)
    
    a = A()
    a.__dict__['d'] = 'dict_d'
    print a.__dict__
    print a.d
    

執行后輸出:

  {'d': 'dict_d'}
  invoking __get__
  12

如果把類Desc__set__()注釋掉,那么它就從“data descriptor”變成了“no-data descriptor”。這樣再次執行一次,輸入:

  {'d': 'dict_d'}
   dict_d

同時定義__get__()__set__(),然后使__set__()方法拋出AttributeError異常,這就成了只讀的描述符

調用描述符

描述符可以直接通過方法名調用,比如d.__get__(obj).
另外,更常見的是通過屬性訪問自動調用,比如:obj.b則從obj的字典中查找b,如果b定義了__get__()方法,__get__(obj)將會通過下面列出的優先級規則被調用。
調用的細節取決于obj是對象還是類。無論是那種,但必須是新式對象或新式類(object的子類是新式類)。
如果是對象,該機制由object.__getattribute__()實現,當b.x時實際執行的是type(b).__dict__['x'].__get__(b, type(b))。優先級是這樣的:"data descriptions" > 實例變量 > "no-data descriptions" > getattr_().
如果是類。該機制由type.__getattribute__()實現,它將B.b轉變為B.__dict__['x'].__get__(None, B)來執行。如果用純Python:

  def __getattribute__(self, key):
      "Emulate type_getattro() in Objects/typeobject.c"
      v = object.__getattribute__(self, key)
      if hasattr(v, '__get__'):
          return v.__get__(None, self)
      return v

(不懂,__getattribute__(),__getattr__,object,type

重點是:

  • 描述符由__getattribute__()調用
  • 重寫__getattribute__()可以阻止描述符自動調用
  • __getattribute__()只可以在新式類和對象中使用
  • object.__getattribute__() and type.__getattribute__() make different calls to __get__().
  • “data descriptions”優先級永遠優于實例字典
  • “non-data descriptors”可能被實例字典覆蓋

Properties

調用property()是一種簡潔的構建數據描述符的方法,可以在訪問屬性時觸發函數調用:property(fget=None, fset=None, fdel=None, doc=None) -> property attribute

class C(object):
    def getx(self): return self.__x
    def setx(self, value): self.__x = value
    def delx(self): del self.__x
    x = property(getx, setx, delx, "I'm the 'x' property.")

x相當于一個描述符

property用Python來現實的話如下:

  class Property(object):
      "Emulate PyProperty_Type() in Objects/descrobject.c"
  
      def __init__(self, fget=None, fset=None, fdel=None, doc=None):
          self.fget = fget
          self.fset = fset
          self.fdel = fdel
          if doc is None and fget is not None:
              doc = fget.__doc__
          self.__doc__ = doc
  
      def __get__(self, obj, objtype=None):
          if obj is None:
              return self
          if self.fget is None:
              raise AttributeError("unreadable attribute")
          return self.fget(obj)
  
      def __set__(self, obj, value):
          if self.fset is None:
              raise AttributeError("can't set attribute")
          self.fset(obj, value)
  
      def __delete__(self, obj):
          if self.fdel is None:
              raise AttributeError("can't delete attribute")
          self.fdel(obj)
  
      def getter(self, fget):
          return type(self)(fget, self.fset, self.fdel, self.__doc__)
  
      def setter(self, fset):
          return type(self)(self.fget, fset, self.fdel, self.__doc__)
  
      def deleter(self, fdel):
          return type(self)(self.fget, self.fset, fdel, self.__doc__)

內建函數property()適用于向用戶開放了讀權限,但修改值需要用方法來處理的時候。

class Cell(object):
    . . .
    def getvalue(self):
        "Recalculate the cell before returning value"
        self.recalc()
        return self._value
    value = property(getvalue)

然后就可以通過Cell("B2").value來獲取值了。
還有這種裝飾器用法:

  class Student(object):
  
      @property
      def score(self):
          return self._score
  
      @score.setter
      def score(self, value):
          if not isinstance(value, int):
              raise ValueError('score must be an integer!')
          if value < 0 or value > 100:
              raise ValueError('score must between 0 ~ 100!')
          self._score = value
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 這篇文章主要是對官方文檔Python HOWTO之屬性描述符的翻譯。由于英語水平有限,基本上都是意譯。 摘要 本篇...
    Syfun閱讀 1,091評論 0 52
  • 實現了__set__(), __get__()或__delete__()的對象,且描述符屬性只能定義在類級別。@p...
    蔣狗閱讀 251評論 0 0
  • 國家電網公司企業標準(Q/GDW)- 面向對象的用電信息數據交換協議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 11,121評論 6 13
  • 時間在指尖流逝,過往如夢似幻。回憶是美好的,誠然,也有痛苦的淚水。年少無知,做了些后悔事,無法彌補,只能懺悔,保證...
    刀筆伐心閱讀 172評論 0 3
  • 2016馬上就要過去了,兩個目標,學習android和學吉他,雖然晚了些,希望能夠靜下心來,堅持下去!加油!
    迷路的哆啦閱讀 265評論 1 0