全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-19 10 分钟 ✍️ juanwangdev

Python 描述符协议

描述符是一个类,通过定义 __get____set____delete__ 方法控制属性访问行为。

描述符类型

  • 数据描述符:定义 __get____set__
  • 非数据描述符:只定义 __get__

基本结构

Python
class MyDescriptor:
    def __get__(self, instance, owner):
        print("获取属性")
        return self.value

    def __set__(self, instance, value):
        print(f"设置属性: {value}")
        self.value = value

    def __delete__(self, instance):
        print("删除属性")
        del self.value


class MyClass:
    attr = MyDescriptor()


obj = MyClass()
obj.attr = 10       # 设置属性: 10
print(obj.attr)     # 获取属性 → 10
del obj.attr        # 删除属性

描述符方法签名

Python
class Descriptor:
    def __get__(self, instance, owner):
        "
        instance: 访问属性的对象实例类访问时为 None
        owner: 属性所在的类
        "
        pass

    def __set__(self, instance, value):
        "
        instance: 设置属性的对象实例
        value: 设置的值
        "
        pass

    def __delete__(self, instance):
        "
        instance: 删除属性的对象实例
        "
        pass

类型检查描述符

Python
class Typed:
    "类型检查描述符"

    def __init__(self, name, expected_type):
        self.name = name
        self.expected_type = expected_type

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if not isinstance(value, self.expected_type):
            raise TypeError(f"{self.name} 必须是 {self.expected_type}")
        instance.__dict__[self.name] = value


class Person:
    name = Typed('name', str)
    age = Typed('age', int)

    def __init__(self, name, age):
        self.name = name
        self.age = age


p = Person("Alice", 25)
print(p.name)       # Alice
p.age = 30          # 正常
p.age = "thirty"    # TypeError: age 必须是 int

非负值描述符

Python
class NonNegative:
    "非负值描述符"

    def __init__(self, name):
        self.name = name

    def __get__(self, instance, owner):
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if value < 0:
            raise ValueError(f"{self.name} 不能为负数")
        instance.__dict__[self.name] = value


class Account:
    balance = NonNegative('balance')

    def __init__(self, balance):
        self.balance = balance


acc = Account(100)
print(acc.balance)  # 100
acc.balance = -50   # ValueError: balance 不能为负数

惰性计算描述符

Python
class LazyProperty:
    "惰性计算描述符(非数据描述符)"

    def __init__(self, func):
        self.func = func
        self.attr_name = func.__name__

    def __get__(self, instance, owner):
        if instance is None:
            return self
        # 计算并缓存到实例 __dict__
        value = self.func(instance)
        instance.__dict__[self.attr_name] = value
        return value


class DataProcessor:
    @LazyProperty
    def expensive_data(self):
        print("计算耗时数据...")
        return [x ** 2 for x in range(10000)]


dp = DataProcessor()
print(dp.expensive_data)  # 计算耗时数据...(第一次调用)
print(dp.expensive_data)  # 直接返回缓存(不再计算)

实现 property

描述符是 @property 的底层实现:

Python
class Property:
    "模拟 property"

    def __init__(self, fget=None, fset=None, fdel=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return self.fget(instance)

    def __set__(self, instance, value):
        if self.fset is None:
            raise AttributeError("不可写")
        self.fset(instance, value)

    def __delete__(self, instance):
        if self.fdel is None:
            raise AttributeError("不可删除")
        self.fdel(instance)

    def setter(self, fset):
        self.fset = fset
        return self

    def deleter(self, fdel):
        self.fdel = fdel
        return self


class Circle:
    def __init__(self, radius):
        self._radius = radius

    @Property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, value):
        if value < 0:
            raise ValueError("半径不能为负")
        self._radius = value


c = Circle(5)
print(c.radius)   # 5
c.radius = 10     # 正常
c.radius = -1     # ValueError

实现 staticmethod 和 classmethod

Python
class StaticMethod:
    "模拟 staticmethod"

    def __init__(self, func):
        self.func = func

    def __get__(self, instance, owner):
        return self.func


class ClassMethod:
    "模拟 classmethod"

    def __init__(self, func):
        self.func = func

    def __get__(self, instance, owner):
        return self.func.__get__(owner, owner)


class Demo:
    @StaticMethod
    def static_func():
        return "static"

    @ClassMethod
    def class_func(cls):
        return f"class: {cls.__name__}"


print(Demo.static_func())    # static
print(Demo.class_func())     # class: Demo
print(Demo().static_func())  # static
print(Demo().class_func())   # class: Demo

属性查找优先级

Python
访问 obj.attr 的顺序:

1. 数据描述符的 __get__(定义 __set__ 或 __delete__)
2. 实例 __dict__['attr']
3. 非数据描述符的 __get__(只定义 __get__)
4. 类 __dict__['attr'] 及继承链
5. __getattr__

优先级验证

Python
class DataDescriptor:
    def __get__(self, instance, owner):
        return "数据描述符"

    def __set__(self, instance, value):
        pass


class NonDataDescriptor:
    def __get__(self, instance, owner):
        return "非数据描述符"


class Test:
    data_desc = DataDescriptor()
    non_data_desc = NonDataDescriptor()


t = Test()
t.data_desc = "实例值"
print(t.data_desc)      # 数据描述符(优先于实例 __dict__)

t.non_data_desc = "实例值"
print(t.non_data_desc)  # 实例值(实例 __dict__ 优先)

描述符存储策略

Python
# 错误:存储在描述符自身(多实例共享)
class BadDescriptor:
    def __init__(self):
        self.value = None

    def __get__(self, instance, owner):
        return self.value

    def __set__(self, instance, value):
        self.value = value


# 正确:存储在实例 __dict__
class GoodDescriptor:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, owner):
        return instance.__dict__.get(self.name)

    def __set__(self, instance, value):
        instance.__dict__[self.name] = value


class Container:
    value = GoodDescriptor('value')


c1 = Container()
c2 = Container()
c1.value = 10
c2.value = 20
print(c1.value)  # 10
print(c2.value)  # 20(独立存储)

set_name 方法

Python 3.6+ 提供自动获取属性名:

text
class AutoNamedDescriptor:
    def __set_name__(self, owner, name):
        self.name = name

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__.get(self.name)

    def __set__(self, instance, value):
        instance.__dict__[self.name] = value


class Point:
    x = AutoNamedDescriptor()  # 自动获取 name = 'x'
    y = AutoNamedDescriptor()  # 自动获取 name = 'y'


p = Point()
p.x = 10
p.y = 20
print(p.x)  # 10

要点总结

要点说明
数据描述符定义 __get__ + __set__,优先于实例字典
非数据描述符只定义 __get__,实例字典优先
存储位置值应存储在实例 __dict__,避免共享
__set_name__Python 3.6+ 自动获取属性名
property本质是非数据描述符

描述符是 Python 属性系统的核心机制,property、classmethod、staticmethod 都是描述符实现。

D:\git2\jwdev\articles\PYTHON\进阶\面向对象编程\描述符协议.md

📝 发现内容有误?点击此处直接编辑

← 上一篇 Python 抽象基类 ABC
下一篇 → Python 私有属性与方法
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库