小編給大家分享一下python描述符是什么,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
創(chuàng)新互聯(lián)堅持“要么做到,要么別承諾”的工作理念,服務領域包括:成都網(wǎng)站制作、網(wǎng)站設計、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣等服務,滿足客戶于互聯(lián)網(wǎng)時代的劍閣網(wǎng)站設計、移動媒體設計的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡建設合作伙伴!
盡管在開發(fā)的過程中,我們沒有直接的使用過描述符,但是它在底層的運用卻是十分頻繁的存在。例如下面的這些:
function
、bound method
、unbound method
裝是器property
、staticmethod
、classmethod
這些是不是都很熟悉?
其實這些都與描述符有著千絲萬縷的聯(lián)系,這樣吧,我們通過下面的文章來探討一下描述符背后的工作原理吧。
什么是描述符?
在我們了解什么是描述符前,我們可以先找一個例子來看一下
class A: x = 10print(A.x) # 10
這個例子很簡單,我們先在類A
中定義一個類屬性x
,然后得出它的值。
除了這種直接定義類屬性的方法外,我們還可以這樣去定義一個類屬性:
class Ten: def __get__(self, obj, objtype=None): return 10class A: x = Ten() # 屬性換成了一個類print(A.x) # 10
我們可以發(fā)現(xiàn),這回的類屬性x
不是一個具體的值了,而是一個類Ten
,通過這個Ten
定義了一個__get__
方法,返回具體的值。
因此可得出:在python中,我們可以把一個類的屬性,托管給一個類,而這樣的屬性就是一個描述符
簡而言之,描述符
是一個綁定行為
屬性
而這又有著什么意思呢?
回想,我們在開發(fā)時,一般情況下,會將行為
叫做什么?行為
即一個方法。
所以我們也可以將描述符
理解為:對象的屬性并非一個具體的值,而是交給了一個方法去定義。
可以想像一下,如果我們用一個方法去定義一個屬性,這么做有什么好處?
有了方法,我們就可以在方法內(nèi)實現(xiàn)自己的邏輯,最簡單的,我們可以根據(jù)不同的條件,在方法內(nèi)給屬性賦予不同的值,就像下面這樣:
class Age: def __get__(self, obj, objtype=None): if obj.name == 'zhangsan': return 20 elif obj.name == 'lisi': return 25 else: return ValueError("unknow")class Person: age = Age() def __init__(self, name): self.name = name p1 = Person('zhangsan')print(p1.age) # 20p2 = Person('lisi')print(p2.age) # 25p3 = Person('wangwu')print(p3.age) # unknow
這個例子中,age
類屬性被另一個類托管了,在這個類的 __get__
中,它會根據(jù) Person
類的屬性 name
,決定 age
是什么值。
通過這樣一個例子,我們可以看到,通過描述符的使用,我們可以輕易地改變一個類屬性的定義方式。
描述符協(xié)議
了解了描述符的定義,現(xiàn)在我們把重點放到托管屬性的類上。
其實,一個類屬性想要托管給一個類,這個類內(nèi)部實現(xiàn)的方法不能是隨便定義的,它必須遵守「描述符協(xié)議」,也就是要實現(xiàn)以下幾個方法:
__get__(self, obj, type=None) -> value
__set__(self, obj, value) -> None
__delete__(self, obj) -> None
只要是實現(xiàn)了以上幾個方法的其中一個,那么這個類屬性就可以稱作描述符。
另外,描述符又可以分為「數(shù)據(jù)描述符」和「非數(shù)據(jù)描述符」:
只定義了 __get___
,叫做非數(shù)據(jù)描述符
除了定義 __get__
之外,還定義了 __set__
或 __delete__
,叫做數(shù)據(jù)描述符
它們兩者有什么區(qū)別,我會在下面詳述。
現(xiàn)在我們來看一個包含 __get__
和 __set__
方法的描述符例子:
# coding: utf8class Age: def __init__(self, value=20): self.value = value def __get__(self, obj, type=None): print('call __get__: obj: %s type: %s' % (obj, type)) return self.value def __set__(self, obj, value): if value <= 0: raise ValueError("age must be greater than 0") print('call __set__: obj: %s value: %s' % (obj, value)) self.value = valueclass Person: age = Age() def __init__(self, name): self.name = name p1 = Person('zhangsan')print(p1.age)# call __get__: obj: <__main__.Person object at 0x1055509e8> type: <class '__main__.Person'># 20print(Person.age)# call __get__: obj: None type: <class '__main__.Person'># 20p1.age = 25# call __set__: obj: <__main__.Person object at 0x1055509e8> value: 25print(p1.age)# call __get__: obj: <__main__.Person object at 0x1055509e8> type: <class '__main__.Person'># 25p1.age = -1# ValueError: age must be greater than 0
在這例子中,類屬性 age
是一個描述符,它的值取決于 Age
類。
從輸出結(jié)果來看,當我們獲取或修改 age
屬性時,調(diào)用了 Age
的 __get__
和 __set__
方法:
當調(diào)用 p1.age
時,__get__
被調(diào)用,參數(shù) obj
是 Person
實例,type
是 type(Person)
當調(diào)用 Person.age
時,__get__
被調(diào)用,參數(shù) obj
是 None
,type
是 type(Person)
當調(diào)用 p1.age = 25
時,__set__
被調(diào)用,參數(shù) obj
是 Person
實例,value
是25
當調(diào)用 p1.age = -1
時,__set__
沒有通過校驗,拋出 ValueError
其中,調(diào)用 __set__
傳入的參數(shù),我們比較容易理解,但是對于 __get__
方法,通過類或?qū)嵗?/strong>調(diào)用,傳入的參數(shù)是不同的,這是為什么?
這就需要我們了解一下描述符的工作原理。
描述符的工作原理
要解釋描述符的工作原理,首先我們需要先從屬性的訪問說起。
在開發(fā)時,不知道你有沒有想過這樣一個問題:通常我們寫這樣的代碼 a.b
,其背后到底發(fā)生了什么?
這里的 a
和 b
可能存在以下情況:
a
可能是一個類,也可能是一個實例,我們這里統(tǒng)稱為對象
b
可能是一個屬性,也可能是一個方法,方法其實也可以看做是類的屬性
其實,無論是以上哪種情況,在 Python 中,都有一個統(tǒng)一的調(diào)用邏輯:
先調(diào)用 __getattribute__
嘗試獲得結(jié)果
如果沒有結(jié)果,調(diào)用 __getattr__
用代碼表示就是下面這樣:
def getattr_hook(obj, name): try: return obj.__getattribute__(name) except AttributeError: if not hasattr(type(obj), '__getattr__'): raise return type(obj).__getattr__(obj, name)
我們這里需要重點關注一下 __getattribute__
,因為它是所有屬性查找的入口,它內(nèi)部實現(xiàn)的屬性查找順序是這樣的:
要查找的屬性,在類中是否是一個描述符
如果是描述符,再檢查它是否是一個數(shù)據(jù)描述符
如果是數(shù)據(jù)描述符,則調(diào)用數(shù)據(jù)描述符的 __get__
如果不是數(shù)據(jù)描述符,則從 __dict__
中查找
如果 __dict__
中查找不到,再看它是否是一個非數(shù)據(jù)描述符
如果是非數(shù)據(jù)描述符,則調(diào)用非數(shù)據(jù)描述符的 __get__
如果也不是一個非數(shù)據(jù)描述符,則從類屬性中查找
如果類中也沒有這個屬性,拋出 AttributeError
異常
寫成代碼就是下面這樣:
# 獲取一個對象的屬性 def __getattribute__(obj, name): null = object() # 對象的類型 也就是實例的類 objtype = type(obj) # 從這個類中獲取指定屬性 cls_var = getattr(objtype, name, null) # 如果這個類實現(xiàn)了描述符協(xié)議 descr_get = getattr(type(cls_var), '__get__', null) if descr_get is not null: if (hasattr(type(cls_var), '__set__') or hasattr(type(cls_var), '__delete__')): # 優(yōu)先從數(shù)據(jù)描述符中獲取屬性 return descr_get(cls_var, obj, objtype) # 從實例中獲取屬性 if hasattr(obj, '__dict__') and name in vars(obj): return vars(obj)[name] # 從非數(shù)據(jù)描述符獲取屬性 if descr_get is not null: return descr_get(cls_var, obj, objtype) # 從類中獲取屬性 if cls_var is not null: return cls_var # 拋出 AttributeError 會觸發(fā)調(diào)用 __getattr__ raise AttributeError(name)
如果不好理解,你最好寫一個程序測試一下,觀察各種情況下的屬性的查找順序。
到這里我們可以看到,在一個對象中查找一個屬性,都是先從 __getattribute__
開始的。
在 __getattribute__
中,它會檢查這個類屬性是否是一個描述符,如果是一個描述符,那么就會調(diào)用它的 __get__
方法。但具體的調(diào)用細節(jié)和傳入的參數(shù)是下面這樣的:
如果 a
是一個實例,調(diào)用細節(jié)為:
type(a).__dict__['b'].__get__(a, type(a))
如果 a
是一個類,調(diào)用細節(jié)為:
a.__dict__['b'].__get__(None, a)
所以我們就能看到上面例子輸出的結(jié)果。
數(shù)據(jù)描述符和非數(shù)據(jù)描述符
了解了描述符的工作原理,我們繼續(xù)來看數(shù)據(jù)描述符和非數(shù)據(jù)描述符的區(qū)別。
從定義上來看,它們的區(qū)別是:
只定義了 __get___
,叫做非數(shù)據(jù)描述符
除了定義 __get__
之外,還定義了 __set__
或 __delete__
,叫做數(shù)據(jù)描述符
此外,我們從上面描述符調(diào)用的順序可以看到,在對象中查找屬性時,數(shù)據(jù)描述符要優(yōu)先于非數(shù)據(jù)描述符調(diào)用。
在之前的例子中,我們定義了 __get__
和 __set__
,所以那些類屬性都是數(shù)據(jù)描述符。
我們再來看一個非數(shù)據(jù)描述符的例子:
class A: def __init__(self): self.foo = 'abc' def foo(self): return 'xyz'print(A().foo) # 輸出什么?
這段代碼,我們定義了一個相同名字的屬性和方法 foo
,如果現(xiàn)在執(zhí)行 A().foo
,你覺得會輸出什么結(jié)果?
答案是 abc
。
為什么打印的是實例屬性 foo
的值,而不是方法 foo
呢?
這就和非數(shù)據(jù)描述符有關系了。
我們執(zhí)行 dir(A.foo)
,觀察結(jié)果:
print(dir(A.foo))# [... '__get__', '__getattribute__', ...]
看到了嗎?A
的 foo
方法其實實現(xiàn)了 __get__
,我們在上面的分析已經(jīng)得知:只定義 __get__
方法的對象,它其實是一個非數(shù)據(jù)描述符,也就是說,我們在類中定義的方法,其實本身就是一個非數(shù)據(jù)描述符。
所以,在一個類中,如果存在相同名字的屬性和方法,按照上面所講的 __getattribute__
中查找屬性的順序,這個屬性就會優(yōu)先從實例中獲取,如果實例中不存在,才會從非數(shù)據(jù)描述符中獲取,所以在這里優(yōu)先查找的是實例屬性 foo
的值。
到這里我們可以總結(jié)一下關于描述符的相關知識點:
描述符必須是一個類屬性
__getattribute__
是查找一個屬性(方法)的入口
__getattribute__
定義了一個屬性(方法)的查找順序:數(shù)據(jù)描述符、實例屬性、非數(shù)據(jù)描述符、類屬性
如果我們重寫了 __getattribute__
方法,會阻止描述符的調(diào)用
所有方法其實都是一個非數(shù)據(jù)描述符,因為它定義了 __get__
描述符的使用場景
了解了描述符的工作原理,那描述符一般用在哪些業(yè)務場景中呢?
在這里我用描述符實現(xiàn)了一個屬性校驗器,你可以參考這個例子,在類似的場景中去使用它。
首先我們定義一個校驗基類 Validator
,在 __set__
方法中先調(diào)用 validate
方法校驗屬性是否符合要求,然后再對屬性進行賦值。
class Validator: def __init__(self): self.data = {} def __get__(self, obj, objtype=None): return self.data[obj] def __set__(self, obj, value): # 校驗通過后再賦值 self.validate(value) self.data[obj] = value def validate(self, value): pass
接下來,我們定義兩個校驗類,繼承 Validator
,然后實現(xiàn)自己的校驗邏輯。
class Number(Validator): def __init__(self, minvalue=None, maxvalue=None): super(Number, self).__init__() self.minvalue = minvalue self.maxvalue = maxvalue def validate(self, value): if not isinstance(value, (int, float)): raise TypeError(f'Expected {value!r} to be an int or float') if self.minvalue is not None and value < self.minvalue: raise ValueError( f'Expected {value!r} to be at least {self.minvalue!r}' ) if self.maxvalue is not None and value > self.maxvalue: raise ValueError( f'Expected {value!r} to be no more than {self.maxvalue!r}' )class String(Validator): def __init__(self, minsize=None, maxsize=None): super(String, self).__init__() self.minsize = minsize self.maxsize = maxsize def validate(self, value): if not isinstance(value, str): raise TypeError(f'Expected {value!r} to be an str') if self.minsize is not None and len(value) < self.minsize: raise ValueError( f'Expected {value!r} to be no smaller than {self.minsize!r}' ) if self.maxsize is not None and len(value) > self.maxsize: raise ValueError( f'Expected {value!r} to be no bigger than {self.maxsize!r}' )
最后,我們使用這個校驗類:
class Person: # 定義屬性的校驗規(guī)則 內(nèi)部用描述符實現(xiàn) name = String(minsize=3, maxsize=10) age = Number(minvalue=1, maxvalue=120) def __init__(self, name, age): self.name = name self.age = age # 屬性符合規(guī)則 p1 = Person('zhangsan', 20)print(p1.name, p1.age)# 屬性不符合規(guī)則 p2 = person('a', 20)# ValueError: Expected 'a' to be no smaller than 3p3 = Person('zhangsan', -1)# ValueError: Expected -1 to be at least 1
現(xiàn)在,當我們對 Person
實例進行初始化時,就可以校驗這些屬性是否符合預定義的規(guī)則了。
function與method
我們再來看一下,在開發(fā)時經(jīng)常看到的 function
、unbound method
、bound method
它們之間到底有什么區(qū)別?
來看下面這段代碼:
class A: def foo(self): return 'xyz'print(A.__dict__['foo']) # <function foo at 0x10a790d70>print(A.foo) # <unbound method A.foo>print(A().foo) # <bound method A.foo of <__main__.A object at 0x10a793050>>
從結(jié)果我們可以看出它們的區(qū)別:
function
準確來說就是一個函數(shù),并且它實現(xiàn)了 __get__
方法,因此每一個 function
都是一個非數(shù)據(jù)描述符,而在類中會把 function
放到 __dict__
中存儲
當 function
被實例調(diào)用時,它是一個 bound method
當 function
被類調(diào)用時, 它是一個 unbound method
function
是一個非數(shù)據(jù)描述符,我們之前已經(jīng)講到了。
而 bound method
和 unbound method
的區(qū)別就在于調(diào)用方的類型是什么,如果是一個實例,那么這個 function
就是一個 bound method
,否則它是一個 unbound method
。
property/staticmethod/classmethod
我們再來看 property
、staticmethod
、classmethod
。
這些裝飾器的實現(xiàn),默認是 C 來實現(xiàn)的。
其實,我們也可以直接利用 Python 描述符的特性來實現(xiàn)這些裝飾器,
property
的 Python 版實現(xiàn):
class property: def __init__(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdel self.__doc__ = doc def __get__(self, obj, objtype=None): if obj is None: return self.fget 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" return self.fset(obj, value) def __delete__(self, obj): if self.fdel is None: raise AttributeError, "can't delete attribute" return 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__)
staticmethod
的 Python 版實現(xiàn):
class staticmethod: def __init__(self, func): self.func = func def __get__(self, obj, objtype=None): return self.func
classmethod
的 Python 版實現(xiàn):
class classmethod: def __init__(self, func): self.func = func def __get__(self, obj, klass=None): if klass is None: klass = type(obj) def newfunc(*args): return self.func(klass, *args) return newfunc
除此之外,你還可以實現(xiàn)其他功能強大的裝飾器。
由此可見,通過描述符我們可以實現(xiàn)強大而靈活的屬性管理功能,對于一些要求屬性控制比較復雜的場景,我們可以選擇用描述符來實現(xiàn)。
以上是“python描述符是什么”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學習更多知識,歡迎關注創(chuàng)新互聯(lián)行業(yè)資訊頻道!
新聞名稱:python描述符是什么
文章分享:http://sd-ha.com/article14/jgsjde.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供建站公司、外貿(mào)建站、網(wǎng)站導航、標簽優(yōu)化、商城網(wǎng)站、網(wǎng)站營銷
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)