网易首页 > 网易号 > 正文 申请入驻

Python:描述符对象

0
分享至

在 Python 的对象模型中,描述符对象(Descriptor Objects)是支撑语言动态特性的核心机制之一。从最基础的属性访问,到复杂的元编程框架(如 Django ORM、SQLAlchemy、Pydantic 的字段系统),描述符始终处于幕后,却决定着属性系统的最终行为。

如果说 __dict__ 体系提供了属性数据的静态存储结构,那么描述符对象就是介入这一结构之上的动态访问控制层。

需要强调的是,描述符不是特殊语法或内建魔法,而是完全遵循 Python 对象模型的普通对象。

一、描述符对象的概念

(1)描述符是对象

在 Python 中,一切皆对象。描述符也不例外:

• 它是某个类的实例

• 拥有自身的类型、属性与方法

• 可以被赋值、传递并存储于 __dict__ 中

d = Descriptor()

在这一层面上,d 与任何普通对象并无区别。

(2)描述符语义的由来

描述符之所以获得特殊语义,并非源于其“身份”,而在于其实现了特定的协议方法,并且位于类属性位置。

当一个对象同时满足以下条件时,在属性访问过程中,就会被解释器识别为描述符对象:

• 实现 __get__()、__set__()、__delete__() 中至少一个

• 作为类属性存在于另一个类的 __dict__ 中

二、描述符的存储位置与作用范围

(1)描述符的存储位置

描述符对象在参与属性访问控制时,必须作为类属性存在于另一个类对象的 __dict__ 中。

    x = D()   # 描述符对象存放在 A.__dict__ 中

此处的 D() 是一个普通对象,但由于它位于 A.__dict__ 中,因此进入属性查找链。

(2)描述符的作用对象

尽管描述符存在于类级别,但其控制的却是:

• 实例属性的访问

• 类属性的访问行为(当 instance is None)

比如:

print(a.x)    # 输出:descriptor

因为该访问会被解释为:

A.__dict__['x'].__get__(a, A)

从语言规范角度看,描述符对象本质上是对的实现。这些协议方法不是“魔法”,而是 Python 在属性查找过程中主动调用的标准接口。

三、描述符对象的分类

根据是否拦截属性写入或删除操作,描述符可分为两类:数据描述符(Data Descriptor)和非数据描述符(Non-data Descriptor)。

(1)数据描述符

定义:实现了 __set__() 和 / 或 __delete__(),通常同时实现了 __get__() 方法。

行为特征:在属性查找顺序中优先级高于实例 __dict__,因此实例无法通过同名属性绕过其控制。

示例:

        obj.__dict__[self.storage_name] = value

作为类属性使用:

    balance = Positive()

访问行为验证:

print(a.__dict__) # 输出 {'_balance': 100, 'balance': -999}

在 Python 的世界里,没有什么能完全阻止一个想要直接操作 __dict__ 的开发者,但描述符能确保通过“正规途径”(即 a.balance = val)进入的数据一定是合法的。真正的保护应将存储名(如 _balance)与属性名(balance)分离。

(2)非数据描述符

定义:仅实现 __get__() 方法。

行为特征:优先级低于实例 __dict__,因此可被实例属性遮蔽。

示例:

        return value

作为类属性使用:

        return 42

访问行为验证:

print(d.value)   # 第二次:直接从 d.__dict__ 取值,42,不再触发描述符

以上示例利用非数据描述符优先级低于实例 __dict__ 的特性实现“惰性求值”:首次访问时触发计算并将结果缓存至实例 __dict__ ;后续访问则因实例属性“遮蔽”了描述符而直接读取缓存,从而有效避免重复计算,优化运行性能。

四、Python 内置的描述符对象

Python 中的大量核心对象,本身就是描述符对象。

(1)函数对象:非数据描述符

类中定义的函数对象本身是非数据描述符。通过其 __get__() 方法,Python 实现了实例方法的自动绑定。

a = A()

当访问方法 foo:

a.foo

本质是:

A.__dict__['foo'].__get__(a, A)

从而生成绑定方法(Bound Method)。

(2)@property:标准数据描述符

@property 返回的是标准的数据描述符对象(实现了 __get__()、__set__() 和 __delete__()),用于将属性访问映射为函数调用。

示例:

        self._age = value

访问行为:

p.age = 30       # 调用 property.__set__

可以这样说,@property 是描述符机制的官方封装版本。

(3)@classmethod 与 @staticmethod

这两个装饰器均返回描述符对象,分别实现对类对象或函数本身的不同绑定策略。

示例:

        return "no binding"

访问验证:

print(Demo().static_method()) # 仍不绑定

classmethod 的描述符在 __get__() 中绑定 owner。staticmethod 的描述符在 __get__() 中直接返回函数。二者都是描述符对象,只是绑定策略不同。

五、描述符对象在属性查找链中的位置

当执行 obj.attr 时,Python 的查找顺序为:

1、类 __dict__ 中的数据描述符

2、实例 obj.__dict__

3、类 __dict__ 中的非数据描述符

4、类 __dict__ 中的普通属性

5、__getattr__() 方法

描述符的“权力”并非绝对,而是由协议与顺序共同决定的。

六、描述符的现代最佳实践:__set_name__

Python 3.6 之后,引入了:

__set_name__(self, owner, name)

__set_name__() 方法在类创建阶段被自动调用,使描述符对象能够获知自身的属性名与所属类。这是当前描述符实现的标准范式。

示例:

        setattr(obj, self.storage_name, value)

描述符作为类属性使用:

    salary = Typed()

此时在类创建过程中,解释器会隐式执行:

Typed.__set_name__(Employee, "salary")

实际访问行为如下:

print(e.salary)   # 输出:8000

底层状态:

e.__dict__ == {"_age": 30, "_salary": 8000}

实际数据存储在实例的 __dict__ 中,而访问路径始终经过类 __dict__ 中的描述符对象。

上例说明:

• Typed() 本身是一个普通对象。

• 它存在于 Employee.__dict__。

• 通过 __set_name__ 获得属性名。

• 通过 __get__ / __set__ 管理实例数据。

• 实例并不直接暴露真实存储字段。

这一结构正是现代描述符实现的标准范式,也是 ORM、字段系统、类型系统中最常见的设计基础。


小结

描述符对象是 Python 属性系统中的关键组成部分。它们以普通对象的形式存在于类 __dict__ 中,通过实现特定协议方法参与属性查找过程,从而实现对属性访问行为的精细控制。理解描述符,有助于全面把握 Python 对象模型与属性机制的设计思想。


点赞有美意,赞赏是鼓励

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
新疆乌鲁木齐一牛排店被查,唯一干净的竟是喂狗碗

新疆乌鲁木齐一牛排店被查,唯一干净的竟是喂狗碗

观威海
2026-04-15 09:24:19
黄蜂队史首次赢附加赛!三球致命失误后献加时准绝杀 送热火出局

黄蜂队史首次赢附加赛!三球致命失误后献加时准绝杀 送热火出局

醉卧浮生
2026-04-15 10:24:31
凌晨3点,世界级大桥轰然倒塌!13死3失联。

凌晨3点,世界级大桥轰然倒塌!13死3失联。

新浪财经
2026-04-14 15:38:34
许家印最后防线崩塌!高院下死命令:20日不交钱就彻底禁言!

许家印最后防线崩塌!高院下死命令:20日不交钱就彻底禁言!

历史伟人录
2026-03-30 18:00:12
徐杰的2+1判罚正确吗?裁判专家给出答案,球迷:不是2+1也属违体

徐杰的2+1判罚正确吗?裁判专家给出答案,球迷:不是2+1也属违体

南海浪花
2026-04-15 06:41:10
毛焦尔胜选后,涉俄乌表态

毛焦尔胜选后,涉俄乌表态

参考消息
2026-04-14 15:32:03
曾志伟深圳办73岁生日派对,逾百艺人到场,与谭咏麟合唱当场泪崩

曾志伟深圳办73岁生日派对,逾百艺人到场,与谭咏麟合唱当场泪崩

笑谈历史阿晡
2026-04-15 09:57:16
死不悔改?国家出手后,又一名人侮辱全红婵,劣行被扒一言难尽

死不悔改?国家出手后,又一名人侮辱全红婵,劣行被扒一言难尽

小徐讲八卦
2026-04-14 06:06:53
中央刚定调!养老金22连涨稳了,但涨幅或许会让你意外

中央刚定调!养老金22连涨稳了,但涨幅或许会让你意外

小兰聊历史
2026-04-15 03:28:55
回台后,岛内民调结果惊人,萧旭岑:国民党“郑丽文路线”已确立

回台后,岛内民调结果惊人,萧旭岑:国民党“郑丽文路线”已确立

生活魔术专家
2026-04-15 04:11:05
苹果手表跑30英里只偏1%,但 Garmin 每秒测心率让专业选手犹豫了

苹果手表跑30英里只偏1%,但 Garmin 每秒测心率让专业选手犹豫了

灰度测试中
2026-04-14 09:09:23
英伟达首席科学家:以前8个人干10个月的活,现在一个晚上搞定了

英伟达首席科学家:以前8个人干10个月的活,现在一个晚上搞定了

顶级大佬思维
2026-04-14 18:13:14
直接上位?缺少4大主力带队完成15分逆转,球迷:杜锋可以下课了

直接上位?缺少4大主力带队完成15分逆转,球迷:杜锋可以下课了

弄月公子
2026-04-14 23:34:26
富二代公司正在阴养死士

富二代公司正在阴养死士

虎嗅APP
2026-04-14 22:21:44
巴基斯坦也没想到,跟着中国混来混去,结果自己也混了一个霸主

巴基斯坦也没想到,跟着中国混来混去,结果自己也混了一个霸主

小樾说历史
2026-04-14 14:52:07
越南高铁正式动工,没用中国技术,中方定下基调,未来接轨广西?

越南高铁正式动工,没用中国技术,中方定下基调,未来接轨广西?

阿纂看事
2026-04-14 14:38:23
斯诺克赛程:决出最后8席32强,张安达率中国5人亮相,2场冠军PK

斯诺克赛程:决出最后8席32强,张安达率中国5人亮相,2场冠军PK

刘姚尧的文字城堡
2026-04-15 07:02:30
许家印,在恒大王国里,过足了官瘾、钱瘾、色瘾,金蝉脱壳玩死了

许家印,在恒大王国里,过足了官瘾、钱瘾、色瘾,金蝉脱壳玩死了

历史伟人录
2026-04-01 17:55:34
越南高铁终于动工!中国方案被弃用,河内留心眼:不接入中方铁路

越南高铁终于动工!中国方案被弃用,河内留心眼:不接入中方铁路

草莓信箱
2026-04-15 00:01:03
最新:乌克兰收复哈尔科夫重镇要地!俄军企图“调转枪头”

最新:乌克兰收复哈尔科夫重镇要地!俄军企图“调转枪头”

项鹏飞
2026-04-14 17:43:53
2026-04-15 10:56:49
MediaTea
MediaTea
专业的数字媒体、新媒体技术
1826文章数 80关注度
往期回顾 全部

科技要闻

手机无死角上网?亚马逊砸百亿硬刚马斯克

头条要闻

海关破获特大走私黄金出境案:金饰近400件 总重2.8kg

头条要闻

海关破获特大走私黄金出境案:金饰近400件 总重2.8kg

体育要闻

带出中超最大黑马!他让球迷们“排队道歉”

娱乐要闻

曾志伟办73岁生日派对,逾百艺人到场

财经要闻

特朗普称美国对伊朗的战争已经结束

汽车要闻

2026广汽科技日有哪些看点?

态度原创

游戏
本地
健康
旅游
公开课

《潜行者2》更新上线 即将开启神秘Lab X18之门

本地新闻

12吨巧克力有难,全网化身超级侦探添乱

干细胞抗衰4大误区,90%的人都中招

旅游要闻

多地旅游场景上新 “体验经济”激发文旅新活力

公开课

李玫瑾:为什么性格比能力更重要?

无障碍浏览 进入关怀版