Python/描述符
描述符(descriptor)是指實現了以下描述符協議中至少一個方法的類:
- __get__(self, instance, owner):訪問屬性時觸發
- __set__(self, instance, value):設置屬性時觸發
- __delete__(self, instance):刪除屬性時觸發
在 Python 中,描述符(Descriptor) 是一種底層機制,用於控制屬性的訪問方式。它是通過實現特定的魔法方法(__get__、__set__ 和 __delete__)來實現的。例如:
class MyDescriptor:
def __get__(self, instance, owner):
print("调用 __get__")
return instance._value
def __set__(self, instance, value):
print("调用 __set__")
instance._value = value
class MyClass:
attr = MyDescriptor()
obj = MyClass()
obj.attr = 42 # 调用 __set__
print(obj.attr) # 调用 __get__,输出 42
在通過類對象或者實例對象讀取屬性attr時,python調用了這個MyDescriptor()實例的__get__方法,給它3個實參值,其中第一個實參值是描述符對象本身,第二個實參值是MyClass實例對象(可以為None),第三個實參值是MyClass類對象。
描述符的應用場景:
- 實現@staticmethod、@classmethod、@property等內置裝飾器語法糖。甚至是__slots__等的實現。
- 屬性驗證(如類型檢查、範圍限制)
- 惰性加載屬性
- 緩存機制
- ORM 框架中的字段定義(如 Django 的 models.Field)
使用描述符,可以讓程序員在引用一個對象屬性時自定義要完成的工作。本質上看,描述符就是一個類,只不過它定義了另一個類中屬性的訪問方式。換句話說,一個類可以將屬性管理全權委託給描述符類。
如下示例一個描述符及引用描述符類的代碼。Descriptors類就是一個描述符,Person是使用描述符的類:
class Descriptors:
def __init__(self, key, value_type):
self.key = key
self.value_type = value_type
def __get__(self, instance, owner):
print("执行Descriptors的get")
return instance.__dict__[self.key]
def __set__(self, instance, value):
print("执行Descriptors的set")
if not isinstance(value, self.value_type):
raise TypeError("参数%s必须为%s"%(self.key, self.value_type))
instance.__dict__[self.key] = value
def __delete__(self, instance):
print("执行Descriptors的delete")
instance.__dict__.pop(self.key)
class Person:
name = Descriptors("name", str)
age = Descriptors("age", int)
def __init__(self, name, age):
self.name = name
self.age = age
person = Person("xiaoming", 15)
print(person.__dict__)
person.name
person.name = "jone"
print(person.__dict__)
#输出:
#执行Descriptors的set
#执行Descriptors的set
#{'name': 'xiaoming', 'age': 15}
#执行Descriptors的get
#执行Descriptors的set
#{'name': 'jone', 'age': 15}
- 至少實現了內置__set__()和__get__()方法的描述符稱為數據描述符;
- 實現了除__set__()以外的方法的描述符稱為非數據描述符。
@property 修飾的屬性、數據描述符或非數據描述符定義的屬性,都是在類體的頂層定義,而不是在 __init__ 等實例方法中賦值,所以它們都被保存在類對象的 __dict__ 中。
通過類對象或者屬性對象使用一個屬性時,查找這個屬性的操作優先級從高到低順序:
- 數據描述符
- 實例屬性:顯然在實例對象的__dict__中
- 非數據描述符
- 類屬性
- 找不到的屬性觸發__getattr__()
所以,用className.VarName=value,則為類屬性;用instanceName.VarName=value,則優先是數據描述符。
在每次查找obj.attr 屬性時,都用類對象的特殊方法 __getattribute__(),調用obj.__getattribute__('attr'):
- 先在 obj 的類及其父類的 __dict__ 中查找名為 attr 的屬性。如果找到,並且它是一個數據描述符(即定義了 __get__ 和 __set__),則調用該描述符的 __get__ 方法,直接返回。
- 如果沒有數據描述符,則查找實例對象自身的 __dict__(即 obj.__dict__)。如果找到了,就直接返回實例屬性的值。
- 如果實例屬性沒有找到,再查找類及其父類的 __dict__。如果找到,並且它是一個非數據描述符(只定義了 __get__),則調用其 __get__ 方法,返回其值。如果只是普通的類屬性(不實現描述符協議),直接返回屬性值。
- 如果以上都沒有找到,則查找類是否定義了 __getattr__ 方法。如果有,則調用 __getattr__(self, attr)。如果沒有定義 __getattr__,則拋出 AttributeError。
當執行 obj.attr = value,Python 實際調用 obj.__setattr__('attr', value)。在 object的__setattr__ 的默認實現:
- 先檢查 obj 的類(及其父類)中有沒有名為 attr 的描述符。如果有,並且它實現了 __set__(即是數據描述符),就調用 該描述符.__set__(obj, value)。
- 如果沒有數據描述符,就把值放進 obj.__dict__。
類對象賦值的默認行為就是直接把屬性寫入類對象自身的 __dict__中,不會觸發數據描述符的 __set__ 方法。描述符的 __set__ 只會在實例賦值(instance.attr = value)時被觸發。