python對象之屬性訪問控制--descriptor

       在定義class時,會經常使用property、classmethod和staticmethod來定義屬性,使屬性具有特殊的訪問功能。如下所示:

class Myclass(object):                                                          
                                                                                
    def func(self, str):                                                        
        print str                                                               
                                                                                
    def get_RMB(self):                                                          
        return self.mone*6                                                      
                                                                                
    def set_dollar(self, value):                                                
        self.mone=value                                                         
                                                                                
    def del_money(self):                                                        
        del self.mone                                                           
                                                                                
    money = myproperty(get_RMB, set_dollar, del_money, 'given dollars, get RMB') 
                                                                                
    def clsfunc(cls,str):                                                       
        print 'clsfunc:',cls,str                                                
    clsfunc = myclassmethod(clsfunc)                                            
                                                                                
    def staticfunc(str):                                                        
        print 'staticfunc:',str                                                 
    staticfunc = mystaticmethod(staticfunc) 

       爲什麼它們能使屬性的訪問變得不同呢?答案在descriptor。上面的property、classmethod、staticmethod都是descriptor。

       那什麼是descriptor呢?

       descriptor是一種實現__get__(), __set__(), __delete__()三個方法的class,所有實現這三個函數,並且支持特定參數的class都是descriptor。下面實現一個descriptor:

class Descriptor(object):                                                       
    def __get__(self, inst, cls):                                               
        print '__get__', inst, cls                                              
                                                                                
    def __set__(self, inst, value):                                             
        print '__set__', inst, value                                            
                                                                                
    def __delete__(self, inst):                                                    
        print '__del__', inst                                                   
                                                                                
class A(object):                                                          
    des=Descriptor()

終端直接結果:

In [2]: a = A()                                                      
                                                                                
In [3]: a.des                                                                   
__get__ <A object at 0x11eb9d0> <class 'A'>               
                                                                                
In [4]: a.des=1                                                                 
__set__ <A object at 0x11eb9d0> 1                                    
                                                                                
In [5]: del a.des                                                               
__del__ <A object at 0x11eb9d0>
                             
In [7]: A.des                                                        
__get__ None <class 'A'>                                             

In [8]: A.des=1                                                      
                                                                                
In [9]: del A.des                                                    
          
從上面的實驗結果可知:

1, 通過實例化對象a讀取des時,執行的是descriptor的__get__()函數,傳參爲(a,A)

2, 給a.des賦值時,執行的是descriptor的__set__()函數,傳參爲(a,value)

3, 刪除a.des時,執行的是descriptor的__delete__()函數,傳參爲(a)

4, 通過class訪問時(A.des),與a.des一樣,調用__get__()函數,不過傳參爲(None, A)

5, 設置和刪除A.des時,不進行任何操作。

       根據實現的接口不同,descriptor又可以分data descriptor和non-data descriptor。data descritor實現了__get__(), __set__(), __delete__()接口,而non-data descriptor只實現了__get__()。這兩者的不同之處在於:設置和刪除a.des時,data descriptor會調用__set__()和__delete__(), 而non-data descriptor表現與正常的對象屬性一樣,會被直接複製和刪除。

有了上面的知識,我們就可以構造自己的property、classmethod、staticmethod了:

class property(object):                                                         
    def __init__(self, getter, setter=None, deller=None, doc=None):             
        self.getter = getter                                                    
        self.setter = setter                                                    
        self.deller = deller
        self.__doc__ = doc                                                         
                                                                                   
    def __get__(self, inst, cls):                                                  
        # for use class to access property                                         
        if not inst:                                                               
            return self                                                            
        return self.getter(inst)                                                   
                                                                                   
    def __set__(self, inst, value):                                                
        if not self.setter:                                                        
            raise AttributeError('xxx')                                            
        self.setter(inst, value)                                                   
                                                                                   
    def __del__(self, inst):                                                       
        if not self.deller:                                                        
            raise AttributeError('xxx')                                            
        self.deller(inst)                                                          
                                                                                   
class classmethod(object):                                                         
    def __init__(self, clsfunc):                                                   
        self.getter = clsfunc                                                      
                                                                                   
    def __get__(self, inst, cls):                                                  
        def func(*arg, **kwargs):                                                  
            return self.getter(cls, *arg, **kwargs)                                
        return func                                                                
                                                                                   
class staticmethod(object):                                                        
    def __init__(self, staticfunc):                                                
        self.getter=staticfunc                                                     
                                                                                
    def __get__(self, inst, cls):                                               
        return self.getter

class Myclass(object):                                                          
                                                                                
    def func(self, str):                                                       
        print str                                                              
                                                                               
    def get_RMB(self):                                                         
        return self.mone*6                                                      
                                                                                
    def set_dollar(self, value):                                               
        self.mone=value                                                         
                                                                                
    def del_money(self):                                                        
        del self.mone                                                           
                                                                                
    money = myproperty(get_RMB, set_dollar, del_money, 'given dollars, get RMB')
                                                                                
    def clsfunc(cls,str):                                                       
        print 'clsfunc:',cls,str                                                
    clsfunc = myclassmethod(clsfunc)                                            
                                                                                
    def staticfunc(str):                                                        
        print 'staticfunc:',str                                                 
    staticfunc = mystaticmethod(staticfunc)

下面來分析一下,一般的成員函數,爲什麼在我們調用時候,第一個self不需要我們傳入。

>>>dir(Myclass.__dict__['func'])
['__call__',
 '__class__',
 '__closure__',
 '__code__',
 '__defaults__',
 '__delattr__',
 '__dict__',
 '__doc__',
 '__format__',
 '__get__',     #說明Myclass的func屬性是一個Non-data descriptor
 '__getattribute__',
 '__globals__',
 '__hash__',
 '__init__',
 '__module__',
 '__name__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 'func_closure',
 'func_code',
 'func_defaults',
 'func_dict',
 'func_doc',
 'func_globals',
 'func_name']
>>>Myclass.__dict__['func']
<function __main__.func>
>>>Myclass.__dict__['func']('dd','hello') #此處可以看到,正常函數對參數的類型沒有要求
hello

>>>Myclass.func
<unbound method Myclass.func>
>>>Myclass.func(Myclass(), 'hello')  # 此處調用的func是descriptor的返回對象,它的第一個參數必須是Myclass的instance,否則會拋TypeError
hello

>>>Myclass().func
<bound method Myclass.func of <__main__.Myclass object at 0x22a5410>>
>>>Myclass().func('hello')  #此處調用的func是descriptor的返回對象,它只需傳遞一個參數
hello

上實驗可知:

1 當通過Myclass.__dict__['func']調用時,它是一個正常的函數(同時也是一個non-data descriptor),不是descriptor的執行結果。

2 當通過Myclass.func調用時,它是func.__get__(None, Myclass)的返回對像(unbound method)

3 通過Myclass().func調用時,它是func.__get__(Myclass(),Myclass)的返回對象(bound method)

      由此可知,class的一般成員函數,其實一個non-data descriptor,所以通過Myclass().func調用時,實際執行的該函數的__get__(),傳參爲(Myclass(), Myclass)。從而解釋了爲什麼調用一般的成員函數時,無需傳入self參數。

      注意所有的函數都滿足這個條件,而並非只在class內部的函數。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章