很久都沒寫 Flask 代碼相關了,想想也真是惭愧,然並卵,這次還是不寫 Flask 相關,不服你來打我啊(就這麼賤,有本事咬我啊
這次我來寫一下 Python 一個很重要的東西,即 Descriptor (描述符)
初識描述符#
老規矩,Talk is cheap,Show me the code. 我們先來看看一段代碼
class Person(object):
""""""
#----------------------------------------------------------------------
def __init__(self, first_name, last_name):
"""Constructor"""
self.first_name = first_name
self.last_name = last_name
#----------------------------------------------------------------------
@property
def full_name(self):
"""
Return the full name
"""
return "%s %s" % (self.first_name, self.last_name)
if __name__=="__main__":
person = Person("Mike", "Driscoll")
print(person.full_name)
# 'Mike Driscoll'
print(person.first_name)
# 'Mike'
這段代大家肯定很熟悉,恩,property
嘛,誰不知道呢,但是 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__)
看起來是不是很複雜,沒事,我們來一步步的看。不過這裡我們首先給出一個結論:Descriptors 是一種特殊 的對象,這種對象實現了 __get__
,__set__
,__delete__
這三個特殊方法。
详解描述符#
说说 Property#
在上文,我們給出了 Propery 實現代碼,現在讓我們來詳細說說這個
class Person(object):
""""""
#----------------------------------------------------------------------
def __init__(self, first_name, last_name):
"""Constructor"""
self.first_name = first_name
self.last_name = last_name
#----------------------------------------------------------------------
@Property
def full_name(self):
"""
Return the full name
"""
return "%s %s" % (self.first_name, self.last_name)
if __name__=="__main__":
person = Person("Mike", "Driscoll")
print(person.full_name)
# 'Mike Driscoll'
print(person.first_name)
# 'Mike'
首先,如果你對裝飾器不了解的話,你可能要去看看這篇文章,簡而言之,在我們正式運行代碼之前,我們的解釋器就會對我們的代碼進行一次掃描,對涉及裝飾器的部分進行替換。類裝飾器同理。在上文中,這段代碼
@Property
def full_name(self):
"""
Return the full name
"""
return "%s %s" % (self.first_name, self.last_name)
會觸發這樣一個過程,即 full_name=Property(full_name)
。然後在我們後面所實例化對象之後我們調用 person.full_name
這樣一個過程其實等價於 person.full_name.__get__(person)
然後進而觸發__get__()
方法裡所寫的 return self.fget(obj)
即原本上我們所編寫的 def full_name
內的執行代碼。
這個時候,同志們可以去思考下 getter()
,setter()
, 以及 deleter()
的具體運行機制了 =。= 如果還是有問題,歡迎在評論裡進行討論。
關於描述符#
還記得之前我們所提到的一個定義麼:Descriptors 是一種特殊的對象,這種對象實現了 __get__
,__set__
,__delete__
這三個特殊方法。然後在 Python 官方文檔的說明中,為了體現描述符的重要性,有這樣一段話:“They are the mechanism behind properties, methods, static methods, class methods, and super (). They are used throughout Python itself to implement the new style classes introduced in version 2.2. ” 簡而言之就是 先有描述符後有天,秒天秒地秒空氣。恩,在新式類中,屬性,方法調用,靜態方法,類方法等都是基於描述符的特定使用。
OK,你可能想問,為什麼描述符是這麼重要呢?別急,我們接著看
使用描述符#
首先請看下一段代碼
class A(object): #注:在 Python 3.x 版本中,對於 new class 的使用不需要顯式的指定從 object 類進行繼承,如果在 Python 2.X(x>2)的版本中則需要
def a(self):
pass
if __name__=="__main__":
a=A()
a.a()
大家都注意到了我們存在著這樣一個語句 a.a()
,好的,現在請大家思考下,我們在調用這個方法的時候發生了什麼?
OK?想出來了麼?沒有?好的我們繼續
首先我們調用一個屬性的時候,不管是成員還是方法,我們都會觸發這樣一個方法用於調用屬性 __getattribute__()
, 在我們的 __getattribute__()
方法中,如果我們嘗試調用的屬性實現了我們的描述符協議,那麼會產生這樣一個調用過程 type(a).__dict__['a'].__get__(b,type(b))
。好的這裡我們又要給出一個結論了:“在這樣一個調用過程中,有這樣一個優先級順序,如果我們所嘗試調用屬性是一個 data descriptors
,那麼不管這個屬性是否存在我們的實例的 __dict__
字典中,優先調用我們描述符裡的 __get__
方法,如果我們所嘗試調用屬性是一個 non data descriptors
,那麼我們優先調用我們實例裡的 __dict__
裡的存在的屬性,如果不存在,則依照相應原則往上查找我們類,父類中的 __dict__
中所包含的屬性,一旦屬性存在,則調用 __get__
方法,如果不存在則調用 __getattr__()
方法”。理解起來有點抽象?沒事,我們馬上會講,不過在這裡,我們先要解釋下 data descriptors
與 non data descriptors
,再來看一個例子。什麼是 data descriptors
與 non data descriptors
呢?其實很簡單,在描述符中同時實現了 __get__
與 __set__
協議的描述符是 data descriptors
,如果只實現了 __get__
協議的則是 non data descriptors
。好了我們現在來看個例子:
import math
class lazyproperty:
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
if instance is None:
return self
else:
value = self.func(instance)
setattr(instance, self.func.__name__, value)
return value
class Circle:
def __init__(self, radius):
self.radius = radius
pass
@lazyproperty
def area(self):
print("Com")
return math.pi * self.radius * 2
def test(self):
pass
if __name__=='__main__':
c=Circle(4)
print(c.area)
好的,讓我們仔細來看看這段代碼,首先類描述符 @lazyproperty
的替換過程,前面已經說了,我們不在重複。接著,在我們第一次調用 c.area
的時候,我們首先查詢實例 c
的 __dict__
中是否存在著 area
描述符,然後發現在 c
中既不存在描述符,也不存在這樣一個屬性,接著我們向上查詢 Circle
中的 __dict__
,然後查找到名為 area
的屬性,同時這是一個 non data descriptors
,由於我們的實例字典內並不存在 area
屬性,那麼我們便調用類字典中的 area
的 __get__
方法,並在 __get__
方法中通過調用 setattr
方法為實例字典註冊屬性 area
。緊接著,我們在後續調用 c.area
的時候,我們能在實例字典中找到 area
屬性的存在,且類字典中的 area
是一個 non data descriptors
,於是我們不會觸發代碼裡所實現的 __get__
方法,而是直接從實例的字典中直接獲取屬性值。
描述符的使用#
描述符的使用面很廣,不過其主要的目的在於讓我們的調用過程變得可控。因此我們在一些需要對我們調用過程實行精細控制的時候,使用描述符,比如我們之前提到的這個例子
class lazyproperty:
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
if instance is None:
return self
else:
value = self.func(instance)
setattr(instance, self.func.__name__, value)
return value
def __set__(self, instance, value=0):
pass
import math
class Circle:
def __init__(self, radius):
self.radius = radius
pass
@lazyproperty
def area(self, value=0):
print("Com")
if value == 0 and self.radius == 0:
raise TypeError("Something went wring")
return math.pi * value * 2 if value != 0 else math.pi * self.radius * 2
def test(self):
pass
利用描述符的特性實現懶加載,再比如,我們可以控制屬性賦值的值
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=None):
if value is None:
raise TypeError("You can`t to set value as None")
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__)
class test():
def __init__(self, value):
self.value = value
@Property
def Value(self):
return self.value
@Value.setter
def test(self, x):
self.value = x
如上面的例子所描述的一樣,我們可以判斷所傳入的值是否有效等等。
總結#
Python 中的描述符可以說是新式類調用鏈中的根基,所有的方法,成員,變量調用時都將會有描述符的介入。同時我們可以利用描述符的特性來將我們的調用過程變得更為可控。這一點,我們可以在很多著名框架中找到這樣的例子。
參考#
1.《Python Cookbook》 8.10 章 P271
2.《Descriptor HowTo Guid》
3.《Python 黑魔法》