news 2026/6/10 3:46:28

Python 描述符专项练习:6 道编程题从入门到精通

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python 描述符专项练习:6 道编程题从入门到精通

配套专栏:Python 全栈修炼之路 第 15 篇《描述符与属性访问控制》

难度分布:⭐ → ⭐⭐ → ⭐⭐ → ⭐⭐⭐ → ⭐⭐⭐ → ⭐⭐⭐⭐

核心覆盖__get__/__set__/__delete__、数据/非数据描述符、属性查找优先级、property本质、弱引用存储、ORM 实战


前言

描述符是 Python 属性访问机制的底层核心,也是propertyclassmethodstaticmethod以及 Django/SQLAlchemy ORM 的实现基础。本练习精选 6 道编程题,从基础描述符到完整 ORM,层层递进,帮你彻底掌握描述符的精髓。


题目一:基础描述符——类型检查与范围限制 ⭐

📌 题目描述

实现两个基础描述符:类型检查描述符和范围限制描述符:

classPerson:name=Typed("name",str)age=Typed("age",int)score=Range("score",0,100)p=Person()p.name="Alice"p.age=25p.score=85print(f"{p.name},{p.age}岁, 分数{p.score}")# Alice, 25岁, 分数85p.age="25"# TypeError: 'age' 必须是 int 类型,而不是 strp.score=101# ValueError: 'score' 必须在 [0, 100] 范围内

💡 编程思路

这道题考察描述符协议基础__get____set____delete__

  1. Typed:在__set__中用isinstance()检查类型,用id(obj)作为 key 在字典中存储每个实例的值,避免所有实例共享同一值。
  2. Range:在__set__中检查数值是否在[min, max]范围内。
  3. obj is None:在__get__中处理通过类访问的情况(返回描述符自身)。

🖥️ 参考代码

classTyped:"""类型检查描述符。"""def__init__(self,name:str,expected_type:type):self.name=name self.expected_type=expected_type self._data:dict[int,any]={}def__get__(self,obj,objtype=None):ifobjisNone:returnselfreturnself._data.get(id(obj))def__set__(self,obj,value):ifnotisinstance(value,self.expected_type):raiseTypeError(f"'{self.name}' 必须是{self.expected_type.__name__}类型,"f"而不是{type(value).__name__}")self._data[id(obj)]=valuedef__delete__(self,obj):ifid(obj)inself._data:delself._data[id(obj)]def__repr__(self):returnf"Typed({self.name},{self.expected_type.__name__})"classRange:"""范围限制描述符。"""def__init__(self,name:str,min_value=None,max_value=None):self.name=name self.min_value=min_value self.max_value=max_value self._data:dict[int,any]={}def__get__(self,obj,objtype=None):ifobjisNone:returnselfreturnself._data.get(id(obj))def__set__(self,obj,value):ifself.min_valueisnotNoneandvalue<self.min_value:raiseValueError(f"'{self.name}' 不能小于{self.min_value}")ifself.max_valueisnotNoneandvalue>self.max_value:raiseValueError(f"'{self.name}' 不能大于{self.max_value}")self._data[id(obj)]=valuedef__delete__(self,obj):ifid(obj)inself._data:delself._data[id(obj)]classReadOnly:"""只读描述符(初始化后不可修改)。"""def__init__(self,name:str,value=None):self.name=name self._data:dict[int,any]={}self._initialized:set[int]=set()def__get__(self,obj,objtype=None):ifobjisNone:returnselfreturnself._data.get(id(obj))def__set__(self,obj,value):ifid(obj)inself._initialized:raiseAttributeError(f"'{self.name}' 是只读属性")self._data[id(obj)]=value self._initialized.add(id(obj))classPerson:name=Typed("name",str)age=Typed("age",int)score=Range("score",0,100)id=ReadOnly("id")def__init__(self,name,age,score,id):self.name=name self.age=age self.score=score self.id=iddef__repr__(self):returnf"Person(name='{self.name}', age={self.age}, score={self.score}, id={self.id})"# 测试if__name__=="__main__":print("=== 基础描述符 ===")p=Person("Alice",25,85,1001)print(p)print("\n=== 类型检查 ===")try:p.age="25"exceptTypeErrorase:print(f"类型错误:{e}")print("\n=== 范围限制 ===")try:p.score=101exceptValueErrorase:print(f"范围错误:{e}")print("\n=== 只读属性 ===")try:p.id=2002exceptAttributeErrorase:print(f"只读错误:{e}")print("\n=== 通过类访问 ===")print(f"Person.name:{Person.name}")print(f"Person.age:{Person.age}")print("\n=== 删除属性 ===")delp.scoreprint(f"删除后 score:{p.score}")print("\n所有测试通过 ✓")

🔗 关联知识点

知识点说明
__get__(self, obj, objtype)获取属性值
__set__(self, obj, value)设置属性值
__delete__(self, obj)删除属性
id(obj)字典存储避免实例间共享
obj is None处理类访问
数据描述符实现__get__+__set__,优先级最高

题目二:数据描述符 vs 非数据描述符 ⭐⭐

📌 题目描述

通过实验验证数据描述符和非数据描述符的优先级差异:

classDataDescriptor:"""数据描述符(__get__ + __set__)"""...classNonDataDescriptor:"""非数据描述符(仅 __get__)"""...classDemo:data_attr=DataDescriptor()non_data_attr=NonDataDescriptor()d=Demo()# 数据描述符:优先级高于实例属性d.data_attr="实例值"# 被描述符拦截print(d.data_attr)# "数据描述符的值"print(d.__dict__)# {}(没有 data_attr)# 非数据描述符:优先级低于实例属性d.non_data_attr="实例值"# 创建实例属性print(d.non_data_attr)# "实例值"(实例属性覆盖了描述符)print(d.__dict__)# {'non_data_attr': '实例值'}

💡 编程思路

这道题考察属性查找优先级链

  1. 数据描述符__get__+__set__)→ 优先级最高,即使实例有同名属性也优先访问描述符。
  2. 实例属性obj.__dict__)→ 优先级次之。
  3. 非数据描述符(仅__get__)→ 优先级低于实例属性,实例属性可以覆盖它。
  4. 通过object.__setattr__强制创建实例属性,绕过描述符拦截。

🖥️ 参考代码

classDataDescriptor:"""数据描述符:__get__ + __set__"""def__init__(self,name):self.name=name self._data:dict[int,any]={}def__get__(self,obj,objtype=None):ifobjisNone:returnselfprint(f" [DataDescriptor.__get__] 访问{self.name}")returnself._data.get(id(obj),f"数据描述符的默认值")def__set__(self,obj,value):print(f" [DataDescriptor.__set__] 设置{self.name}={value!r}")self._data[id(obj)]=valuedef__delete__(self,obj):print(f" [DataDescriptor.__delete__] 删除{self.name}")ifid(obj)inself._data:delself._data[id(obj)]classNonDataDescriptor:"""非数据描述符:仅 __get__"""def__init__(self,name):self.name=namedef__get__(self,obj,objtype=None):ifobjisNone:returnselfprint(f" [NonDataDescriptor.__get__] 访问{self.name}")returnf"非数据描述符的值"classDemo:data_attr=DataDescriptor("data_attr")non_data_attr=NonDataDescriptor("non_data_attr")classPriorityDemo:"""属性查找优先级演示。"""defdemonstrate(self):d=Demo()print("=== 1. 初始访问 ===")print(f"data_attr:{d.data_attr}")print(f"non_data_attr:{d.non_data_attr}")print("\n=== 2. 尝试设置数据描述符 ===")d.data_attr="实例值"print(f"设置后 data_attr:{d.data_attr}")print(f"__dict__:{d.__dict__}")print("\n=== 3. 尝试设置非数据描述符 ===")d.non_data_attr="实例值"print(f"设置后 non_data_attr:{d.non_data_attr}")print(f"__dict__:{d.__dict__}")print("\n=== 4. 强制创建实例属性(绕过数据描述符) ===")object.__setattr__(d,"data_attr","强制实例值"
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 3:46:18

Exercise003_Even_or_Odd

Exercise 3: Even or Odd 题目重现 原题标题&#xff1a;Even or Odd 原题描述&#xff1a;Write a program that asks the user to enter a number and then determines whether the number is even or odd. 中文说明&#xff1a;编写一个程序&#xff0c;要求用户输入一个数字…

作者头像 李华
网站建设 2026/6/10 3:42:50

从一次CANoe测试失败案例,聊聊CAPL变量作用域那些容易忽略的细节

从一次CANoe测试失败案例&#xff0c;聊聊CAPL变量作用域那些容易忽略的细节那天下午三点&#xff0c;实验室的空调嗡嗡作响&#xff0c;我盯着屏幕上CANoe测试报告中那个诡异的"0xFE"错误码&#xff0c;咖啡已经凉了第三杯。作为负责整车ECU网络通信测试的工程师&am…

作者头像 李华
网站建设 2026/6/10 3:35:42

Rocky Linux 10.2 全面解析:企业级 CentOS 替代方案及保姆级docker安装

1. 引言&#xff1a;Rocky Linux 的使命与定位 Rocky Linux 是一个由社区驱动的企业级开源操作系统&#xff0c;旨在作为 Red Hat Enterprise Linux (RHEL) 的 100% 兼容替代品。它由 CentOS 联合创始人 Gregory Kurtzer 发起&#xff0c;在 Red Hat 宣布停止 CentOS Linux 稳定…

作者头像 李华