7 描述器
1 概念
- 用于描述一個屬性對應操作的對象。
- 屬性對應操作一般為:增/改示辈、刪、查
2 作用
- 可以代為管理一個類屬性的讀寫刪操作, 在相關方法中, 對數(shù)據(jù)進行驗證處理, 過濾處理等等
- 如果一個類屬性被定義為描述器魄幕,那么以后對這個類屬性的操作(讀寫刪), 都將由這個描述器代理
3 定義
3.1 通過 property
創(chuàng)建屬性的描述器
其實就是前面提到的 property 的使用
- 一般我們定義類都會將屬性定義為私有屬性躏救,這樣外界就不能隨便訪問或賦值
class Person:
def __init__(self, value):
self.__age = value
- 讓實例能夠通過
.age
方式進行訪問或過濾性修改 - 通過 property 定義后返回的 age 就是一個描述器,描述器就是一個對屬性操作進行描述(即過濾或保護等)的對象
class Person:
def __init__(self, value):
self.__age = value
def get_age(self):
print("get age")
return self.__age
def set_age(self, value):
print("set age")
if value < 0:
value = 0
self.__age = value
def del_age(self):
print("del age")
del self.__age
# 此時返回的 age 就是一個描述器肌访,描述器就是一個對屬性操作進行描述(即過濾或保護等)的對象
age = property(get_age, set_age, del_age)
p = Person()
# 訪問
print(p.age)
# 賦值
p.age = 19
# 刪除
del p.age
- 查看上面代碼中通過property 定義的 age 描述器與 name 屬性分區(qū)對別
age 是被分配到 Data descriptors defined here: 區(qū)
class Person:
# 與 age 對比用
name = "fkm"
def __init__(self, value):
self.__age = value
def get_age(self):
print("get age")
return self.__age
def set_age(self, value):
print("set age")
if value < 0:
value = 0
self.__age = value
def del_age(self):
print("del age")
del self.__age
age = property(get_age, set_age, del_age)
p = Person(10)
print(Person.__dict__)
print(p.__dict__)
print("-" * 20)
help(Person)
>>>> 打印結果
{
'__module__': '__main__',
'name': 'fkm',
'__init__': <function Person.__init__ at 0x10c86ebf8>,
'get_age': <function Person.get_age at 0x10c86eb70>,
'set_age': <function Person.set_age at 0x10c86ec80>,
'del_age': <function Person.del_age at 0x10c86ed08>,
'age': <property object at 0x10c6bea98>,
'__dict__': <attribute '__dict__' of 'Person' objects>,
'__weakref__': <attribute '__weakref__' of 'Person' objects>,
'__doc__': None
}
{'_Person__age': 10}
--------------------
Help on class Person in module __main__:
class Person(builtins.object)
| Methods defined here:
|
| __init__(self, value)
| Initialize self. See help(type(self)) for accurate signature.
|
| del_age(self)
|
| get_age(self)
|
| set_age(self, value)
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
|
| age
|
| ----------------------------------------------------------------------
| Data and other attributes defined here:
|
| name = 'fkm'
Process finished with exit code 0
- 使用 property 內(nèi)置方式找默,同樣可以通過屬性描述器訪問屬性
class Person:
def __init__(self):
self.__age = 10
@property
def age(self):
return self.__age
@age.setter
def age(self, value):
if value < 0:
value = 0
self.__age = value
@age.deleter
def age(self):
print("del age")
del self.__age
# p = Person()
# # 訪問
# print(p.age)
# # 賦值
# p.age = 19
# # 刪除
# del p.age
help(Perosn)
# age 同樣在 Data descriptors defined here: 區(qū)
>>>>> 打印結果
Help on class Person in module __main__:
class Person(builtins.object)
| Methods defined here:
|
| __init__(self)
| Initialize self. See help(type(self)) for accurate signature.
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
|
| age
Process finished with exit code 0
3.2 通過屬性實例化方式 - 創(chuàng)建屬性描述器
3.2.1 該實例的類必須要實現(xiàn)以下三個方法
__get__
__set__
__delete__
- 優(yōu)點
- 上述3.1創(chuàng)建屬性描述器方式,具有導致該類臃腫的弊端吼驶,試想:實現(xiàn)一個 age 屬性描述器已經(jīng)在 Person 類里面寫了3個方法了惩激,如果再多些屬性,則會出現(xiàn)3*x 個方法蟹演。
- 而通過屬性實例化方式风钻,則可以將對該屬性進行操作描述的3個方法抽離到該實例類里面,更加面向?qū)ο罅恕?/li>
- 這樣的屬性實例化方式也體現(xiàn) python 語言一切皆對象的特性
# 屬性描述器對象
class Age:
def __get__(self, instance, owner):
print("get")
def __set__(self, instance, value):
print("set")
def __delete__(self, instance):
print("delete")
# 類
class Person:
age = Age() # 屬性描述器實例化
# 此時 age 也是一個類屬性酒请,但之能通過類執(zhí)行 get 方法骡技,其他方法不會被轉(zhuǎn)傳到描述器中
# 屬性操作
p = Person()
p.age = 10
print(p.age)
# del p.age
>>> 打印結果
set
get
None
3.2.2 通過屬性實例化調(diào)用描述器時,使用注意事項:
使用實例進行調(diào)用
最多三個方法都會被調(diào)用使用類進行調(diào)用
最多會調(diào)用get方法-
不能順利轉(zhuǎn)換場景
3.1 新式類和經(jīng)典類
描述器僅在新式類(繼承自 object 的)中生效羞反,且類及描述器類都是新式類3.2 方法攔截
* 一個實例屬性的正常訪問順序1 實例對象自身的__dict__字典 2 對應類對象的__dict__字典 3 如果有父類, 會再往上層的__dict__字典中檢測 4 如果沒找到, 又定義了__getattr__方法, 就會調(diào)用這個方法
而在上述的整個過程當中, 是如何將描述器的__get__方法給嵌入到查找機制當中?
就是通過這個方法進行實現(xiàn):__getattribute__
內(nèi)部實現(xiàn)模擬
如果實現(xiàn)了描述器方法get就會直接調(diào)用
如果沒有, 則按照上面的機制去查找
4 描述器-和實例屬性同名時, 操作優(yōu)先級
資料描述器:描述器類同時實現(xiàn)了 get set 方法
非資料描述器:僅僅實現(xiàn)了 get 方法
-
資料描述器 > 實例屬性 > 非資料描述器
1 資料描述器 > 實例屬性 測試
class Age(object):
def __get__(self, instance, owner):
print("get")
def __set__(self, instance, value):
print("set")
def __delete__(self, instance):
print("delete")
class Person(object):
age = Age()
def __init__(self):
self.age = 10
p = Person()
p.age = 10
print(p.age)
print(p.__dict__)
>>>> 打印結果
set
set
get
None
{}
- 實例屬性 > 非資料描述器 測試
class Age(object):
def __get__(self, instance, owner):
print("get")
class Person(object):
age = Age()
def __init__(self):
self.age = 10
p = Person()
p.age = 10
print(p.age)
print(p.__dict__)
>>>> 打印結果
10
{'age': 10}
5 描述器-值的存儲問題
- 描述器Age是共享的
class Age:
def __get__(self, instance, owner):
print("get", self, instance, owner)
if "v" in instance.__dict__:
return instance.v
def __set__(self, instance, value):
print("set", self, instance, value)
instance.v = value
def __delete__(self, instance):
print("delete", self, instance)
del instance.v
class Person:
age = Age()
p1 = Person()
print(p1.age)
p2 = Person()
print(p2.age)
>>>> 打印結果
get <__main__.Age object at 0x107e049e8> <__main__.Person object at 0x107e04a20> <class '__main__.Person'>
get <__main__.Age object at 0x107e049e8> <__main__.Person object at 0x107e04a58> <class '__main__.Person'>
# 只有 instance 的值是對應 Person 實例布朦,而 Age 描述器則是同一個
- 所以應該把值存放到對應的類實例中
class Age:
def __get__(self, instance, owner):
print("get", self, instance, owner)
if "v" in instance.__dict__:
return instance.v
def __set__(self, instance, value):
print("set", self, instance, value)
instance.v = value
def __delete__(self, instance):
print("delete", self, instance)
del instance.v
class Person:
age = Age()
p1 = Person()
p1.age = 10
print(p1.age)
p2 = Person()
p2.age = 19
print(p2.age)
>>>> 打印結果
set <__main__.Age object at 0x10457b9e8> <__main__.Person object at 0x10457ba20> 10
get <__main__.Age object at 0x10457b9e8> <__main__.Person object at 0x10457ba20> <class '__main__.Person'>
10
set <__main__.Age object at 0x10457b9e8> <__main__.Person object at 0x10457ba58> 19
get <__main__.Age object at 0x10457b9e8> <__main__.Person object at 0x10457ba58> <class '__main__.Person'>
19
- 有些場景也可以共享 描述器哦,如你想保存最新被修改的值時昼窗,就應該將值綁定到共享的描述器中
self.v = value
是趴,那么以后這個值就是一個共享值,哪個實例修改后膏秫,其他實例獲取到的就是被新修改的值
問題
- 解析:描述器的定義方式為類屬性形式右遭,但我們操作使用為什么都是通過對象來調(diào)用做盅?
- 類屬性是被各個對象共享的,那么有如何保證不同的對象可以操作到這個共享屬性的不同值窘哈?