• <del id="a8uas"></del>
    • 千鋒教育-做有情懷、有良心、有品質的職業教育機構

      400-811-9990
      手機站
      千鋒教育

      千鋒學習站 | 隨時隨地免費學

      千鋒教育

      掃一掃進入千鋒手機站

      領取全套視頻
      千鋒教育

      關注千鋒學習站小程序
      隨時隨地免費學習課程

      上海
      • 北京
      • 鄭州
      • 武漢
      • 成都
      • 西安
      • 沈陽
      • 廣州
      • 南京
      • 深圳
      • 大連
      • 青島
      • 杭州
      • 重慶
      當前位置:合肥千鋒IT培訓  >  技術干貨  >  Python之描述符

      Python之描述符

      來源:千鋒教育
      發布人:xqq
      時間: 2023-11-05 18:48:43

      Descriptors(描述符)是Python語言中一個深奧但很重要的一個黑魔法,它被廣泛應用于Python語言的內核,熟練掌握描述符將會為Python程序員的工具箱添加一個額外的技巧。本文我將講述描述符的定義以及一些常見的場景,并且在文末會補充一下__getattr,__getattribute__,__getitem__這三個同樣涉及到屬性訪問的魔術方法。

      描述符的定義

      descr__get__(self,obj,objtype=None)-->value

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

      descr.__delete__(self,obj)-->None

      只要一個objectattribute(對象屬性)定義了上面三個方法中的任意一個,那么這個類就可以被稱為描述符類。

      描述符基礎

      下面這個例子中我們創建了一個RevealAcess類,并且實現了__get__方法,現在這個類可以被稱為一個描述符類。

      classRevealAccess(object):

      def__get__(self,obj,objtype):

      print('selfinRevealAccess:{}'.format(self))

      print('self:{}\nobj:{}\nobjtype:{}'.format(self,obj,objtype))

      classMyClass(object):

      x=RevealAccess()

      deftest(self):

      print('selfinMyClass:{}'.format(self))

      EX1實例屬性

      接下來我們來看一下__get__方法的各個參數的含義,在下面這個例子中,self即RevealAccess類的實例x,obj即MyClass類的實例m,objtype顧名思義就是MyClass類自身。從輸出語句可以看出,m.x訪問描述符x會調用__get__方法。

      >>>m=MyClass()

      >>>m.test()

      selfinMyClass:<__main__.MyClassobjectat0x7f19d4e42160>

      >>>m.x

      selfinRevealAccess:<__main__.RevealAccessobjectat0x7f19d4e420f0>

      self:<__main__.RevealAccessobjectat0x7f19d4e420f0>

      obj:<__main__.MyClassobjectat0x7f19d4e42160>

      objtype:

      EX2類屬性

      如果通過類直接訪問屬性x,那么obj接直接為None,這還是比較好理解,因為不存在MyClass的實例。

      >>>MyClass.x

      selfinRevealAccess:<__main__.RevealAccessobjectat0x7f53651070f0>

      self:<__main__.RevealAccessobjectat0x7f53651070f0>

      obj:None

      objtype:

      描述符的原理

      描述符觸發

      上面這個例子中,我們分別從實例屬性和類屬性的角度列舉了描述符的用法,下面我們來仔細分析一下內部的原理:

      如果是對實例屬性進行訪問,實際上調用了基類object的__getattribute__方法,在這個方法中將obj.d轉譯成了type(obj).__dict__['d'].__get__(obj,type(obj))。

      如果是對類屬性進行訪問,相當于調用了元類type的__getattribute__方法,它將cls.d轉譯成cls.__dict__['d'].__get__(None,cls),這里__get__()的obj為的None,因為不存在實例。

      簡單講一下__getattribute__魔術方法,這個方法在我們訪問一個對象的屬性的時候會被無條件調用,詳細的細節比如和__getattr,__getitem__的區別我會在文章的末尾做一個額外的補充,我們暫時并不深究。

      描述符優先級

      首先,描述符分為兩種:

      如果一個對象同時定義了__get__()和__set__()方法,則這個描述符被稱為datadescriptor。

      如果一個對象只定義了__get__()方法,則這個描述符被稱為non-datadescriptor。

      我們對屬性進行訪問的時候存在下面四種情況:

      datadescriptor

      instancedict

      non-datadescriptor

      __getattr__()

      它們的優先級大小是:

      datadescriptor>instancedict>non-datadescriptor>__getattr__()

      這是什么意思呢?就是說如果實例對象obj中出現了同名的datadescriptor->d和instanceattribute->d,obj.d對屬性d進行訪問的時候,由于datadescriptor具有更高的優先級,Python便會調用type(obj).__dict__['d'].__get__(obj,type(obj))而不是調用obj.__dict__[‘d’]。但是如果描述符是個non-datadescriptor,Python則會調用obj.__dict__['d']。

      Property

      每次使用描述符的時候都定義一個描述符類,這樣看起來非常繁瑣。Python提供了一種簡潔的方式用來向屬性添加數據描述符。

      property(fget=None,fset=None,fdel=None,doc=None)->propertyattribute

      fget、fset和fdel分別是類的getter、setter和deleter方法。我們通過下面的一個示例來說明如何使用Property:

      classAccount(object):

      def__init__(self):

      self._acct_num=None

      defget_acct_num(self):

      returnself._acct_num

      defset_acct_num(self,value):

      self._acct_num=value

      defdel_acct_num(self):

      delself._acct_num

      acct_num=property(get_acct_num,set_acct_num,del_acct_num,'_acct_numproperty.')

      如果acct是Account的一個實例,acct.acct_num將會調用getter,acct.acct_num=value將調用setter,delacct_num.acct_num將調用deleter。

      >>>acct=Account()

      >>>acct.acct_num=1000

      >>>acct.acct_num

      1000

      Python也提供了@property裝飾器,對于簡單的應用場景可以使用它來創建屬性。一個屬性對象擁有getter,setter和deleter裝飾器方法,可以使用它們通過對應的被裝飾函數的accessor函數創建屬性的拷貝。

      classAccount(object):

      def__init__(self):

      self._acct_num=None

      @property

      #the_acct_numproperty.thedecoratorcreatesaread-onlyproperty

      defacct_num(self):

      returnself._acct_num

      @acct_num.setter

      #the_acct_numpropertysettermakesthepropertywriteable

      defset_acct_num(self,value):

      self._acct_num=value

      @acct_num.deleter

      defdel_acct_num(self):

      delself._acct_num

      如果想讓屬性只讀,只需要去掉setter方法。

      在運行時創建描述符

      我們可以在運行時添加property屬性:

      classPerson(object):

      defaddProperty(self,attribute):

      #createlocalsetterandgetterwithaparticularattributename

      getter=lambdaself:self._getProperty(attribute)

      setter=lambdaself,value:self._setProperty(attribute,value)

      #constructpropertyattributeandaddittotheclass

      setattr(self.__class__,attribute,property(fget=getter,\

      fset=setter,\

      doc="Auto-generatedmethod"))

      def_setProperty(self,attribute,value):

      print("Setting:{}={}".format(attribute,value))

      setattr(self,'_'+attribute,value.title())

      def_getProperty(self,attribute):

      print("Getting:{}".format(attribute))

      returngetattr(self,'_'+attribute)

      >>>user=Person()

      >>>user.addProperty('name')

      >>>user.addProperty('phone')

      >>>user.name='johnsmith'

      Setting:name=johnsmith

      >>>user.phone='12345'

      Setting:phone=12345

      >>>user.name

      Getting:name

      'JohnSmith'

      >>>user.__dict__

      {'_phone':'12345','_name':'JohnSmith'}

      靜態方法和類方法

      我們可以使用描述符來模擬Python中的@staticmethod和@classmethod的實現。我們首先來瀏覽一下下面這張表:

      靜態方法

      對于靜態方法f。c.f和C.f是等價的,都是直接查詢object.__getattribute__(c,‘f’)或者object.__getattribute__(C,’f‘)。靜態方法一個明顯的特征就是沒有self變量。

      靜態方法有什么用呢?假設有一個處理專門數據的容器類,它提供了一些方法來求平均數,中位數等統計數據方式,這些方法都是要依賴于相應的數據的。但是類中可能還有一些方法,并不依賴這些數據,這個時候我們可以將這些方法聲明為靜態方法,同時這也可以提高代碼的可讀性。

      使用非數據描述符來模擬一下靜態方法的實現:

      classStaticMethod(object):

      def__init__(self,f):

      self.f=f

      def__get__(self,obj,objtype=None):

      returnself.f

      我們來應用一下:

      classMyClass(object):

      @StaticMethod

      defget_x(x):

      returnx

      print(MyClass.get_x(100))#output:100

      類方法

      Python的@classmethod和@staticmethod的用法有些類似,但是還是有些不同,當某些方法只需要得到類的引用而不關心類中的相應的數據的時候就需要使用classmethod了。

      使用非數據描述符來模擬一下類方法的實現:

      classClassMethod(object):

      def__init__(self,f):

      self.f=f

      def__get__(self,obj,klass=None):

      ifklassisNone:

      klass=type(obj)

      defnewfunc(*args):

      returnself.f(klass,*args)

      returnnewfunc

      其他的魔術方法

      首次接觸Python魔術方法的時候,我也被__get__,__getattribute__,__getattr__,__getitem__之間的區別困擾到了,它們都是和屬性訪問相關的魔術方法,其中重寫__getattr__,__getitem__來構造一個自己的集合類非常的常用,下面我們就通過一些例子來看一下它們的應用。

      __getattr__

      Python默認訪問類/實例的某個屬性都是通過__getattribute__來調用的,__getattribute__會被無條件調用,沒有找到的話就會調用__getattr__。如果我們要定制某個類,通常情況下我們不應該重寫__getattribute__,而是應該重寫__getattr__,很少看見重寫__getattribute__的情況。

      從下面的輸出可以看出,當一個屬性通過__getattribute__無法找到的時候會調用__getattr__。

      In[1]:classTest(object):

      ...:def__getattribute__(self,item):

      ...:print('call__getattribute__')

      ...:returnsuper(Test,self).__getattribute__(item)

      ...:def__getattr__(self,item):

      ...:return'call__getattr__'

      ...:

      In[2]:Test().a

      call__getattribute__

      Out[2]:'call__getattr__'

      應用

      對于默認的字典,Python只支持以obj['foo']形式來訪問,不支持obj.foo的形式,我們可以通過重寫__getattr__讓字典也支持obj['foo']的訪問形式,這是一個非常經典常用的用法:

      classStorage(dict):

      """AStorageobjectislikeadictionaryexceptobj.foocanbeusedinadditiontoobj['foo']."""

      def__getattr__(self,key):

      try:

      returnself[key]

      exceptKeyErrorask:

      raiseAttributeError(k)

      def__setattr__(self,key,value):

      self[key]=value

      def__delattr__(self,key):

      try:

      delself[key]

      exceptKeyErrorask:

      raiseAttributeError(k)

      def__repr__(self):

      return''!

      我們來使用一下我們自定義的加強版字典:

      >>>s=Storage(a=1)

      >>>s['a']

      1

      >>>s.a

      1

      >>>s.a=2

      >>>s['a']

      2

      >>>dels.a

      >>>s.a

      ...

      AttributeError:'a'

      __getitem__

      getitem用于通過下標[]的形式來獲取對象中的元素,下面我們通過重寫__getitem__來實現一個自己的list。

      classMyList(object):

      def__init__(self,*args):

      self.numbers=args

      def__getitem__(self,item):

      returnself.numbers[item]

      my_list=MyList(1,2,3,4,6,5,3)

      printmy_list[2]

      這個實現非常的簡陋,不支持slice和step等功能,請讀者自行改進,這里我就不重復了。

      應用

      下面是參考requests庫中對于__getitem__的一個使用,我們定制了一個忽略屬性大小寫的字典類。

      程序有些復雜,我稍微解釋一下:由于這里比較簡單,沒有使用描述符的需求,所以使用了@property裝飾器來代替,lower_keys的功能是將實例字典中的鍵全部轉換成小寫并且存儲在字典self._lower_keys中。重寫了__getitem__方法,以后我們訪問某個屬性首先會將鍵轉換為小寫的方式,然后并不會直接訪問實例字典,而是會訪問字典self._lower_keys去查找。賦值/刪除操作的時候由于實例字典會進行變更,為了保持self._lower_keys和實例字典同步,首先清除self._lower_keys的內容,以后我們重新查找鍵的時候再調用__getitem__的時候會重新新建一個self._lower_keys。

      classCaseInsensitiveDict(dict):

      @property

      deflower_keys(self):

      ifnothasattr(self,'_lower_keys')ornotself._lower_keys:

      self._lower_keys=dict((k.lower(),k)forkinself.keys())

      returnself._lower_keys

      def_clear_lower_keys(self):

      ifhasattr(self,'_lower_keys'):

      self._lower_keys.clear()

      def__contains__(self,key):

      returnkey.lower()inself.lower_keys

      def__getitem__(self,key):

      ifkeyinself:

      returndict.__getitem__(self,self.lower_keys[key.lower()])

      def__setitem__(self,key,value):

      dict.__setitem__(self,key,value)

      self._clear_lower_keys()

      def__delitem__(self,key):

      dict.__delitem__(self,key)

      self._lower_keys.clear()

      defget(self,key,default=None):

      ifkeyinself:

      returnself[key]

      else:

      returndefault

      我們來調用一下這個類:

      >>>d=CaseInsensitiveDict()

      >>>d['ziwenxie']='ziwenxie'

      >>>d['ZiWenXie']='ZiWenXie'

      >>>print(d)

      {'ZiWenXie':'ziwenxie','ziwenxie':'ziwenxie'}

      >>>print(d['ziwenxie'])

      ziwenxie

      #d['ZiWenXie']=>d['ziwenxie']

      >>>print(d['ZiWenXie'])

      ziwenxie

      以上內容為大家介紹了Python之描述符,希望對大家有所幫助,如果想要了解更多Python相關知識,請關注多測師。http://www.mobiletrain.org/xwzx/

      聲明:本站稿件版權均屬千鋒教育所有,未經許可不得擅自轉載。

      猜你喜歡LIKE

      Python查看模塊中的屬性

      2023-11-05

      Python之三目運算

      2023-11-05

      Python 之模塊重載的五種方法

      2023-11-05

      最新文章NEW

      使用Black自由格式化Python

      2023-11-05

      入門Python的4大陷阱

      2023-11-05

      python經典最短代碼實現排序的功能

      2023-11-05

      相關推薦HOT

      更多>>

      快速通道 更多>>

      最新開班信息 更多>>

      網友熱搜 更多>>