在定義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內部的函數。