欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Python中的Descriptor描述符學(xué)習(xí)教程

 更新時(shí)間:2016年06月02日 16:44:12   作者:piglei  
簡(jiǎn)單來(lái)說(shuō),數(shù)據(jù)描述符是指實(shí)現(xiàn)了__get__、__set__、__del__方法的類(lèi)屬性,等效于定義了三個(gè)方法的接口,下面就來(lái)詳細(xì)看一下Python中的Descriptor修飾符學(xué)習(xí)教程

Descriptor是什么?簡(jiǎn)而言之,Descriptor是用來(lái)定制訪問(wèn)類(lèi)或?qū)嵗某蓡T的一種協(xié)議。額。。好吧,一句話是說(shuō)不清楚的。下面先介紹一下Python中成員變量的定義和使用。
我們知道,在Python中定義類(lèi)成員和C/C++相比得到的結(jié)果具有很大的差別。如下面的定義:

 class Cclass
 {
   int I;
   void func();
 };
 
 Cclass c;

在上面的定義中,C++定義了一個(gè)類(lèi)型,所有該類(lèi)型的對(duì)象都包含有一個(gè)成員整數(shù)i和函數(shù)func;而Python則創(chuàng)建了一個(gè)名為Pclass、類(lèi)型(__class__)為type(詳情請(qǐng)參見(jiàn)MetaClass,Python中一切皆為對(duì)象,類(lèi)型也不例外)的對(duì)象,然后再創(chuàng)建一個(gè)名為p、類(lèi)型為Pclass的對(duì)象。如下所示:

 In [71]: type(pclass)
 Out[71]: <type 'type'>
 In [72]: p = pclass()
 In [73]: type(p)
 Out[73]: <class '__main__.pclass'>

p和Pclass各自包含了一些成員,如下所示:
1 p.__class__ p.__init__ p.__sizeof__
2 p.__delattr__ p.__module__ p.__str__
3 p.__dict__ p.__new__ p.__subclasshook__
4 p.__doc__ p.__reduce__ p.__weakref__
5 p.__format__ p.__reduce_ex__ p.f
6 p.__getattribute__ p.__repr__ p.i
7 p.__hash__ p.__setattr__

其中,帶有雙下劃線的成員為特殊成員,或者可以稱(chēng)之為固定成員(和__slots__定義的成員類(lèi)似),這些成員變量的值可以被改變,但不能被刪除(del)。其中,__class__變量為對(duì)象所屬的類(lèi)型,__doc__為對(duì)象的文檔字符串。有一個(gè)特殊成員值得注意:__dict__,該字典中保存了對(duì)象的自定義變量。相信大家在初學(xué)Python對(duì)于其中對(duì)象可以任意增加刪除成員變量的能力感到驚訝,其實(shí)這個(gè)功能的玄機(jī)就在于__dict__成員中(注意type的__dict__為dictproxy類(lèi)型):

 In [10]: p.x = 2
 In [11]: p.__dict__
 Out[11]: {'x': 2}

通過(guò)上面的演示可以很清楚地看出:Python將對(duì)象的自定義成員以鍵值對(duì)的形式保存到__dict__字典中,而前面提到的類(lèi)型定義只是這種情況的語(yǔ)法糖而已,即上面的類(lèi)型定義等價(jià)于以下形式的定義:

 Class Pclass(object): pass
 Pclass.i = 1
 Pclass.f = lambda x: x

訪問(wèn)成員變量時(shí),Python也是從__dict__字典中取出變量名對(duì)應(yīng)的值,如下形式的兩種訪問(wèn)形式是等價(jià)的——在Descriptor被引入之前:

 p.i
 p.__dict__['i']

Descriptor的引入即將改變上面的規(guī)則,且看下文分解。
定義:Descriptor Protocol
    Descriptor如何改變對(duì)象成員的訪問(wèn)規(guī)則呢?根據(jù)計(jì)算機(jī)理論中“絕大多數(shù)軟件問(wèn)題都可以用增加一個(gè)中間層的方式解決”這一名言,我們需要為對(duì)象訪問(wèn)提供一個(gè)中間層,而非直接訪問(wèn)所需的對(duì)象。實(shí)現(xiàn)這一中間層的方式是定義Descriptor協(xié)議。Descriptor的定義很簡(jiǎn)單,如果一個(gè)類(lèi)包含以下三個(gè)方法(之一),則可以稱(chēng)之為一個(gè)Descriptor:

1.object.__get__(self, instance, owner)

成員被訪問(wèn)時(shí)調(diào)用,instance為成員所屬的對(duì)象、owner為instance所屬的類(lèi)型

2.object.__set__(self, instance, value)

成員被賦值時(shí)調(diào)用

3.0object.__delete__(self, instance)

成員被刪除時(shí)調(diào)用

如果我們需要改變一個(gè)對(duì)象在其它對(duì)象中的訪問(wèn)規(guī)則,需要將其定義成Descriptor,之后在對(duì)該成員進(jìn)行訪問(wèn)時(shí)將調(diào)用該Descriptor的相應(yīng)函數(shù)。下面是一個(gè)使用Descriptor改變?cè)L問(wèn)規(guī)則的例子:

 class MyDescriptor(object):
   def __init__(self, x):
     self.x = x
   def __get__(self, instance, owner):
     print 'get from descriptor'
     return self.x
   def __set__(self, instance, value):
     print 'set from descriptor'
     self.x = value
   def __delete__(self, instance)
     print 'del from descriptor, the val is', self.x
 
 class C(object):
   d = MyDescriptor('hello')
 
 >> C.d
 get from descriptor
 
 >> c = C()
 >> c.d
 get from descriptor
 
 >> c.d = 1
 set from descriptor
 
 >> del c.d
 del from descriptor, the val is 1

從例子中可以看出:當(dāng)我們對(duì)對(duì)象成員進(jìn)行引用(Reference)、賦值(Assign)和刪除(Dereference)操作時(shí),如果對(duì)象成員為一個(gè)Descriptor,則這些操作將執(zhí)行該Descriptor對(duì)象的相應(yīng)成員函數(shù)。以上約定即為Descriptor協(xié)議。

obj.name背后的魔法
引入了Descriptor之后,Python對(duì)于對(duì)象成員訪問(wèn)的規(guī)則是怎樣的呢?在回答這一問(wèn)題之前,需要對(duì)Descriptor進(jìn)行簡(jiǎn)單的劃分:

Overriding或Data:對(duì)象同時(shí)提供了__get__和__set__方法

Nonoverriding或Non-Data:對(duì)象僅提供了__get__方法

(__del__方法表示自己被忽略了,很傷心~)

下面是從一個(gè)類(lèi)對(duì)象中訪問(wèn)其成員(如C.name)的規(guī)則:

如果“name”在C.__dict__能找到,C.name將訪問(wèn)C.__dict__['name'],假設(shè)為v。如果v是一個(gè)Descriptor,則返回type(v).__get__(v, None, C),否則直接返回v;

如果“name”不在C.__dict__中,則向上查找C的父類(lèi),根據(jù)MRO(Method Resolution Order)對(duì)C的父類(lèi)重復(fù)第一步;

還是沒(méi)有找到“name”,拋出AttributeError異常。

從一個(gè)類(lèi)實(shí)例對(duì)象中訪問(wèn)其成員(如x.name,type(x)為C)要稍微復(fù)雜一些:

如果“name”能在C(或C的父類(lèi))中找到,且其值v為一個(gè)Overriding Descriptor,則返回type(v).__get__(v, x, C)的值;

否則,如果“name”能在x.__dict__中找到,則返回x.__dict__['name']的值;

如果“name”仍未找到,則執(zhí)行類(lèi)對(duì)象成員的查找規(guī)則;

如果C定義了__getattr__函數(shù),則調(diào)用該函數(shù);否則拋出AttributeError異常。

成員賦值的查找規(guī)則與訪問(wèn)規(guī)則類(lèi)似,但還是有一點(diǎn)區(qū)別:對(duì)類(lèi)成員執(zhí)行賦值操作時(shí)將直接設(shè)置C.__dict__中的值,而不會(huì)調(diào)用Descriptor的__set__函數(shù)。

以上面的代碼為例,當(dāng)訪問(wèn)C.d時(shí),Python將在C.__dict__中找到d,并且發(fā)現(xiàn)d是一個(gè)Descriptor,因此將調(diào)用d.__get__(None, C);當(dāng)訪問(wèn)c.d時(shí),Python首先查找C,并且在其中發(fā)現(xiàn)d的定義,且d為一個(gè)Overriding Descriptor,因此執(zhí)行d.__get__(c, C)。

前面介紹了Descriptor的一些細(xì)節(jié),那么Descriptor的作用是什么呢?在Python中,Descriptor主要用來(lái)實(shí)現(xiàn)一些Python本身的功能,如類(lèi)方法調(diào)用、staticmethod和Property等。下面將對(duì)這些使用Descriptor進(jìn)行類(lèi)方法調(diào)用的實(shí)現(xiàn)進(jìn)行介紹。

Bound & Unbound Method
在python中,函數(shù)是第一級(jí)的對(duì)象,即其本質(zhì)與其它對(duì)象相同,差別在于函數(shù)對(duì)象是callable對(duì)象,即對(duì)于函數(shù)對(duì)象f,可以用語(yǔ)法f()來(lái)調(diào)用函數(shù)。上面提到的對(duì)象成員訪問(wèn)規(guī)則,對(duì)于函數(shù)來(lái)說(shuō)是完全一樣的。Python在實(shí)現(xiàn)成員函數(shù)調(diào)用時(shí)obj.f()時(shí),會(huì)執(zhí)行一下兩個(gè)步驟:

根據(jù)對(duì)象成員訪問(wèn)規(guī)則獲取函數(shù)對(duì)象;

用函數(shù)對(duì)象執(zhí)行函數(shù)調(diào)用;

為了驗(yàn)證上述過(guò)程,我們可以執(zhí)行以下代碼:
 

Class C(object):
   def f(self):
     pass
 >> fun = C.f
 Unbound Method
 >> fun()
 >> c = C()
 >> fun = c.f
 Bound Method
 >> fun()

我們可以看到C.f和c.f返回了instancemethod類(lèi)型的對(duì)象,這兩個(gè)對(duì)象也是可調(diào)用的,但是卻不是我們本以為的func對(duì)象。那么instancemethod對(duì)象和func對(duì)象之間具有什么關(guān)聯(lián)呢?

func類(lèi)型:func類(lèi)型為Python中原始的函數(shù)對(duì)象類(lèi)型,即def f(): pass將定義一個(gè)func類(lèi)型的對(duì)象f;

instancemethod:func的一個(gè)wrapper,如果類(lèi)方法沒(méi)有綁定到對(duì)象,則該instancemethod為一個(gè)Unbound Method,對(duì)Unbound Method的調(diào)用將導(dǎo)致TypeError錯(cuò)誤;如果類(lèi)方法綁定到了對(duì)象,則該instancemethod為一個(gè)Bound Method,對(duì)Bound Method的調(diào)用不許要指定self參數(shù)的值。

如果查看Unbound Method對(duì)象和Bound Method對(duì)象的成員,我們可以發(fā)現(xiàn)它們都包含了一下三個(gè)成員:im_func、im_self和im_class。其中im_func為所封裝的func對(duì)象,im_self則為所綁定對(duì)象的值,而im_class則為定義該函數(shù)的類(lèi)對(duì)象。由此我們可以知道,Python會(huì)根據(jù)不同的情況返回函數(shù)的不同wrapper,當(dāng)通過(guò)類(lèi)對(duì)象訪問(wèn)函數(shù)時(shí),返回的是名為Unbound Method對(duì)象的Wrapper,而通過(guò)類(lèi)實(shí)例訪問(wèn)函數(shù)是,返回的則是綁定了該實(shí)例的名為Bound Method對(duì)象的Wrapper。

現(xiàn)在是Descriptor大顯身手的時(shí)候了。

Python中將func定義為一個(gè)Overriding Descriptor,在其__get__方法中構(gòu)造一個(gè)instancemethod對(duì)象,并根據(jù)被訪問(wèn)函數(shù)被訪問(wèn)的情況設(shè)置im_func、im_self和im_class成員。在instancemethod實(shí)例被調(diào)用時(shí),則根據(jù)im_func和im_self來(lái)完成真正的函數(shù)調(diào)用。演示這一過(guò)程的代碼如下:

 Class instancemethod(object):
   def __call__(self, *args):
     if self.im_self == None:
     raise 'unbound error'
     return self.im_func(self.im_self, *args)
   def __init__(self, im_self, im_func, im_class):
     self.im_self = im_self
     self.im_func = im_func
     self.im_class = im_class
 
 class func(object):
   ...
   def __get__(self, instance, owner):
     return instancemethod(instance, self, owner)
   def __set__(self, instance, value):
     pass
   ...

一個(gè)小問(wèn)題的解決
分享一下剛遇到的一個(gè)小問(wèn)題,我有一段類(lèi)似于這樣的python代碼:

# coding: utf-8

class A(object):

  @property
  def _value(self):
#    raise AttributeError("test")
    return {"v": "This is a test."}

  def __getattr__(self, key):
    print "__getattr__:", key
    return self._value[key]

if __name__ == '__main__':
  a = A()
  print a.v

運(yùn)行后可以得到正確的結(jié)果

__getattr__: v
This is a test.

但是注意,如果把

#    raise AttributeError("test")

這行的注釋去掉的話,即在_value方法里面拋出AttributeError異常,事情就會(huì)變得有些奇怪。程序運(yùn)行的時(shí)候并不會(huì)拋出異常,而是會(huì)進(jìn)入一個(gè)無(wú)限遞歸:

File "attr_test.py", line 12, in __getattr__
  return self._value[key]
 File "attr_test.py", line 12, in __getattr__
  return self._value[key]
RuntimeError: maximum recursion depth exceeded while calling a Python object

通過(guò)多方查找后發(fā)現(xiàn)是property裝飾器的問(wèn)題,property實(shí)際上是一個(gè)descriptor。在python doc中可以發(fā)現(xiàn)這樣的文字:

object.__get__(self, instance, owner)

Called to get the attribute of the owner class (class attribute access) or of an instance of that class (instance attribute access). owner is always the owner class, while instance is the instance that the attribute was accessed through, or None when the attribute is accessed through the owner. This method should return the (computed) attribute value or raise an AttributeError exception.

這樣當(dāng)用戶訪問(wèn)._value時(shí),拋出了AttributeError從而調(diào)用了__getattr__方法去嘗試獲取。這樣程序就變成了無(wú)限遞歸。

這個(gè)問(wèn)題看上去不復(fù)雜,但是當(dāng)你的_value方法是比較隱晦的拋出AttributeError的話,調(diào)試起來(lái)就會(huì)比較困難了。


小結(jié)
Descriptor是訪問(wèn)對(duì)象成員時(shí)的一個(gè)中間層,為我們提供了自定義對(duì)象成員訪問(wèn)的方式。通過(guò)對(duì)Descriptor的探索,對(duì)原來(lái)的一些看似神秘的概念頓時(shí)有種豁然開(kāi)朗的感覺(jué):

類(lèi)方法調(diào)用:編譯器并沒(méi)有為其提供專(zhuān)門(mén)的語(yǔ)法規(guī)則,而是使用Descriptor返回instancemethod來(lái)封裝func,從而實(shí)現(xiàn)類(lèi)似obj.func()的調(diào)用方式;

staticmethod:decorator將創(chuàng)建一個(gè)StaticMethod并在其中保存func對(duì)象,StaticMethod是一個(gè)Descriptor,其__get__函數(shù)中返回前面所保存的func對(duì)象;

Property:創(chuàng)建一個(gè)Property對(duì)象,在其__get__、__set__和__delete__方法中分別執(zhí)行構(gòu)造對(duì)象是傳入的fget、fset、和fdel函數(shù)?,F(xiàn)在知道為什么Property只提供這三個(gè)函數(shù)作為參數(shù)么。。

最后一個(gè)問(wèn)題是,Python引入Descriptor之后的性能會(huì)不會(huì)有影響?性能影響是必須的:每次訪問(wèn)成員時(shí)的查找規(guī)則,之后再調(diào)用Descriptor的__get__函數(shù),如果是方法調(diào)用的話之后才是執(zhí)行真正的函數(shù)調(diào)用。每次訪問(wèn)對(duì)象成員時(shí)都要經(jīng)歷以上過(guò)程,對(duì)Python的性能應(yīng)該會(huì)有較大的影響。但是,在Python的世界,貌似Pythonic才是被關(guān)注的重點(diǎn),性能神馬的就別提了。。

相關(guān)文章

最新評(píng)論