一般來(lái)說(shuō),一個(gè)描述器是一個(gè)有“綁定行為”的對(duì)象屬性 (object attribute),它的訪問(wèn)控制被描述器協(xié)議方法重寫(xiě)。這些方法是 __get__(), __set__() , 和 __delete__() 。有這些方法的對(duì)象叫做描述器。
默認(rèn)對(duì)屬性的訪問(wèn)控制是從對(duì)象的字典里面 (__dict__) 中獲取 (get) , 設(shè)置 (set) 和刪除 (delete) 。舉例來(lái)說(shuō), a.x 的查找順序是, a.__dict__['x'] , 然后 type(a).__dict__['x'] , 然后找 type(a) 的父類 ( 不包括元類 (metaclass) ).如果查找到的值是一個(gè)描述器, Python 就會(huì)調(diào)用描述器的方法來(lái)重寫(xiě)默認(rèn)的控制行為。這個(gè)重寫(xiě)發(fā)生在這個(gè)查找環(huán)節(jié)的哪里取決于定義了哪個(gè)描述器方法。注意, 只有在新式類中時(shí)描述器才會(huì)起作用。在之前的篇節(jié)中已經(jīng)提到新式類和舊式類的,有興趣可以查看之前的篇節(jié)來(lái)看看,至于新式類最大的特點(diǎn)就是所有類都繼承自 type 或者 object 的類。
在面向?qū)ο缶幊虝r(shí),如果一個(gè)類的屬性有相互依賴的關(guān)系時(shí),使用描述器來(lái)編寫(xiě)代碼可以很巧妙的組織邏輯。在 Django 的 ORM 中,models.Model中的 InterField 等字段, 就是通過(guò)描述器來(lái)實(shí)現(xiàn)功能的。
我們先看下下面的例子:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
class User(object):
def __init__(self, name='兩點(diǎn)水', sex='男'):
self.sex = sex
self.name = name
def __get__(self, obj, objtype):
print('獲取 name 值')
return self.name
def __set__(self, obj, val):
print('設(shè)置 name 值')
self.name = val
class MyClass(object):
x = User('兩點(diǎn)水', '男')
y = 5
if __name__ == '__main__':
m = MyClass()
print(m.x)
print('\n')
m.x = '三點(diǎn)水'
print(m.x)
print('\n')
print(m.x)
print('\n')
print(m.y)
輸出的結(jié)果如下:
獲取 name 值
兩點(diǎn)水
設(shè)置 name 值
獲取 name 值
三點(diǎn)水
獲取 name 值
三點(diǎn)水
5
通過(guò)這個(gè)例子,可以很好的觀察到這 __get__() 和 __set__() 這些方法的調(diào)用。
再看一個(gè)經(jīng)典的例子
我們知道,距離既可以用單位"米"表示,也可以用單位"英尺"表示。 現(xiàn)在我們定義一個(gè)類來(lái)表示距離,它有兩個(gè)屬性: 米和英尺。
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
class Meter(object):
def __init__(self, value=0.0):
self.value = float(value)
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
self.value = float(value)
class Foot(object):
def __get__(self, instance, owner):
return instance.meter * 3.2808
def __set__(self, instance, value):
instance.meter = float(value) / 3.2808
class Distance(object):
meter = Meter()
foot = Foot()
if __name__ == '__main__':
d = Distance()
print(d.meter, d.foot)
d.meter = 1
print(d.meter, d.foot)
d.meter = 2
print(d.meter, d.foot)
輸出的結(jié)果:
0.0 0.0
1.0 3.2808
2.0 6.5616
在上面例子中,在還沒(méi)有對(duì) Distance 的實(shí)例賦值前, 我們認(rèn)為 meter 和 foot 應(yīng)該是各自類的實(shí)例對(duì)象, 但是輸出卻是數(shù)值。這是因?yàn)?__get__ 發(fā)揮了作用.
我們只是修改了 meter ,并且將其賦值成為 int ,但 foot 也修改了。這是 __set__ 發(fā)揮了作用.
描述器對(duì)象 (Meter、Foot) 不能獨(dú)立存在, 它需要被另一個(gè)所有者類 (Distance) 所持有。描述器對(duì)象可以訪問(wèn)到其擁有者實(shí)例的屬性,比如例子中 Foot 的 instance.meter 。