
本文深入探讨了python描述符在使用过程中,当描述符内部用于存储数据的属性名与外部管理属性名相同时,为何会导致无限递归的问题。通过剖析描述符协议的调用机制,揭示了这种命名冲突如何触发`__set__`方法的循环调用。文章提供了清晰的代码示例,并强调了使用不同内部属性名(如加前缀`_`)来安全实现描述符的关键实践,旨在帮助开发者避免此类常见陷阱,提升代码的健壮性。
Python 描述符(Descriptor)是一种强大的机制,允许开发者自定义对象属性的访问、修改和删除行为。它通过实现特殊方法 __get__、__set__ 和 __delete__ 来工作。当一个类属性被赋值为一个描述符实例时,对该类实例上同名属性的访问就会被描述符的方法所拦截。
例如,我们可以创建一个描述符来记录属性的访问和修改日志:
import logging
logging.basicConfig(level=logging.INFO)
class LoggedAgeAccess:
"""
一个记录年龄属性访问和修改的描述符。
它将实际的年龄值存储在拥有者对象的 _age 属性中。
"""
def __get__(self, obj, objtype=None):
if obj is None:
return self # 如果通过类访问,返回描述符本身
value = obj._age # 从拥有者对象的私有属性中获取值
logging.info('Accessing %r giving %r', 'age', value)
return value
def __set__(self, obj, value):
logging.info('Updating %r to %r', 'age', value)
obj._age = value # 将值存储到拥有者对象的私有属性中
class Person:
age = LoggedAgeAccess() # 将 LoggedAgeAccess 实例作为 Person 类的 age 属性
def __init__(self, name, age):
self.name = name
self.age = age # 调用描述符的 __set__ 方法
def birthday(self):
self.age += 1 # 调用描述符的 __get__ 和 __set__ 方法
在上述示例中,LoggedAgeAccess 描述符管理 Person 类的 age 属性。它将实际的年龄值存储在 Person 实例的 _age 属性中。当我们创建 Person 实例并设置 age 时,LoggedAgeAccess.__set__ 方法会被调用;当我们访问 age 时,LoggedAgeAccess.__get__ 方法会被调用。
mary = Person('Mary M', 30)
# 输出: INFO:root:Updating 'age' to 30
print(vars(mary))
# 输出: {'name': 'Mary M', '_age': 30}可以看到,age 属性通过描述符被管理,实际的数据存储在 _age 中,避免了命名冲突。
然而,如果我们在描述符内部使用与外部管理属性相同的名称来存储数据,就会导致一个常见的陷阱——无限递归。
考虑以下修改后的 LoggedAgeAccess 描述符,它尝试将值存储在 obj.age 而不是 obj._age 中:
class LoggedAgeAccessBroken:
"""
一个错误的描述符实现,将导致无限递归。
它尝试将实际的年龄值存储在拥有者对象的 age 属性中。
"""
def __get__(self, obj, objtype=None):
if obj is None:
return self
# 问题所在:访问 obj.age 会再次触发描述符的 __get__ 方法
value = obj.age
logging.info('Accessing %r giving %r', 'age', value)
return value
def __set__(self, obj, value):
logging.info('Updating %r to %r', 'age', value)
# 问题所在:设置 obj.age 会再次触发描述符的 __set__ 方法
obj.age = value
class PersonBroken:
age = LoggedAgeAccessBroken() # 将 LoggedAgeAccessBroken 实例作为 PersonBroken 类的 age 属性
def __init__(self, name, age):
self.name = name
self.age = age # 尝试调用描述符的 __set__ 方法
当我们尝试使用 PersonBroken 类时:
# mary_broken = PersonBroken('Mary B', 30)程序会陷入一个无限循环,不断输出 INFO:root:Updating 'age' to 30,最终可能导致栈溢出错误。
Copymatic
Cowriter是一款AI写作工具,可以通过为你生成内容来帮助你加快写作速度和激发写作灵感。
149
查看详情
这个无限递归的根本原因在于 Python 的描述符协议。当一个类的属性(例如 PersonBroken.age)是一个描述符实例时,对该类的实例属性(例如 mary_broken.age)的访问或设置,都会首先通过描述符协议进行处理。
具体来说:
同样的问题也存在于 __get__ 方法中。如果 __get__ 方法内部尝试访问 obj.age,也会再次触发描述符的 __get__ 方法,导致无限递归。
解决这个问题的关键在于,描述符内部用于存储实际数据的属性名,必须与描述符所管理的外部属性名不同。通常的做法是,在拥有者对象上使用一个带前缀(如 _ 或 __)的私有属性名来存储数据。
回到最初的正确示例,LoggedAgeAccess 描述符通过 obj._age 来存储和访问数据,从而避免了与外部 age 属性的冲突。
import logging
logging.basicConfig(level=logging.INFO)
class LoggedAgeAccess:
"""
正确的描述符实现,使用不同的内部属性名来存储数据。
"""
def __get__(self, obj, objtype=None):
if obj is None:
return self
# 访问拥有者对象的私有属性 _age
value = obj._age
logging.info('Accessing %r giving %r', 'age', value)
return value
def __set__(self, obj, value):
logging.info('Updating %r to %r', 'age', value)
# 将值存储到拥有者对象的私有属性 _age
obj._age = value
class Person:
age = LoggedAgeAccess()
def __init__(self, name, age):
self.name = name
self.age = age # 正常调用描述符的 __set__ 方法
def birthday(self):
self.age += 1 # 正常调用描述符的 __get__ 和 __set__ 方法
除了在拥有者对象上使用不同的私有属性名外,描述符还可以使用其他方式来存储数据,例如:
Python 描述符是实现高级属性管理和元编程的强大工具。然而,在使用它们时,必须注意避免内部存储属性与外部管理属性同名的问题,这会导致无限递归。通过在拥有者对象上使用不同的、通常是带前缀的私有属性名来存储实际数据,可以有效地解决这一问题,确保描述符的正确和安全运行。掌握这一关键概念,将帮助开发者编写更健壮、更符合 Python 惯例的代码。
以上就是Python描述符中属性名称冲突导致无限递归的解析与实践的详细内容,更多请关注其它相关文章!
# 它将
# 泸州废品回收网站推广
# 行业网站建设产品有哪些
# 木地板推广营销策略
# 抖音seo系统方式
# 常州网站排行优化
# 英文网站建设注意什么
# 舟山商城网站建设报价
# 新闻网站建设平台
# 天津网站建设制作报价
# 大鱼关键词排名
# 是一种
# 法会
# python
# 下划线
# 浮点
# 就会
# 这一
# 当我们
# 是一个
# 递归
# asic
# 栈
# 工具
# access
相关栏目:
【
Google疑问12 】
【
Facebook疑问10 】
【
优化推广96088 】
【
技术知识133117 】
【
IDC资讯59369 】
【
网络运营7196 】
【
IT资讯61894 】
相关推荐:
composer licenses 命令:如何检查项目依赖的许可证?
PHP页面重载后变量状态保持:实现用户档案连续浏览的教程
《kimi智能助手》制作ppt教程
MySQL多重关联查询:利用别名高效获取同一表的多个关联字段
风神瞳获取全攻略
手机耗电快是什么原因 延长手机电池续航时间的设置方法【详解】
重返未来:1999卡戎全方位攻略
汽车之家网页版免费登录_汽车之家官网首页直接进入
批改网官网首页登录 批改网学生用户登录入口
掌握产品代码正则表达式:避免常见陷阱与精确匹配
Lar*el怎么实现全文搜索_Lar*el Scout集成Algolia教程
4399小游戏下装链接 4399小游戏下载链接入口
无人机考证官网 中国民航无人机考证官网登录入口
firefox火狐浏览器最新官网主页_ firefox火狐浏览器平台入口直达官方链接
PHP odbc_fetch_array 返回值处理:如何正确访问嵌套数组元素
Golang如何实现HTTP请求重试机制_Golang HTTP请求错误处理策略
《绝区零》2.3前瞻|直播|内容介绍
以下哪一个是适应长期护理制度发展而设立的新职业
虫虫漫画排行榜单入口_虫虫漫画编辑推荐入口
红手指专业版app注册教程
店铺如何做视频号推广?做视频号推广有用吗?
DeepSeek超全面指南:入门必看
视频号视频怎么提取文案?提取的文案如何优化与使用?
word邮件合并怎么插入个性化图片_Word邮件合并插入个性化图片方法
Excel如何制作月度销售统计图_Excel动态图表制作与控件应用
优酷官网登录入口电脑版 优酷官网网址入口
mysql导入sql文件能分批导入吗_mysql分批次导入大sql文件的实用技巧
从J*a应用程序中导出MySQL表数据的技术指南
菜鸟驿站的取件码忘了怎么办 手机快速查询指南
易车网官网直达入口 易车网在线登录入口
CDR如何复制交互式填充色
Leaflet地图弹出窗口图片动态显示:避免缺失图标的专业指南
PHP中动态类名访问的类实例类型提示与静态分析实践
yandex网页版直接登录 yandex官方入口平台访问方法
php如何实现多域名共享session_php存储session到redis与跨域读取配置
CodeIgniter 3 中基于 MySQL 数据高效生成动态图表教程
抖音作品被限流怎么办 抖音内容优化与流量恢复方法
使用逻辑应用(Logic Apps)自动处理邮件附件中的XML到Excel
铁路12306座位怎么选_12306官方选座操作方法
Excel怎么用XLOOKUP函数实现双向查找_ExcelXLOOKUP替代VLOOKUP+HLOOKUP的高级用法
Google Drive API 认证:服务账户与OAuth 2.0的选择与实践
铁拳8在线玩 铁拳8在线秒玩入口
Flash AS3.0简易相册制作
《猎聘》筛选猎头岗位方法
抄漫画官网防走失地址_抄漫画最新漫画完整版阅读入口
React应用中Commerce.js数据加载与状态管理最佳实践
口腔诊所管理软件推荐
荣耀Magic7拍照夜景噪点处理_荣耀Magic7相机优化
Python定时发送QQ消息
NumPy 高性能技巧:基于多列条件查找最近邻行索引的向量化实现
2025-11-10
运城市盐湖区信雨科技有限公司是一家深耕海外推广领域十年的专业服务商,作为谷歌推广与Facebook广告全球合作伙伴,聚焦外贸企业出海痛点,以数字化营销为核心,提供一站式海外营销解决方案。公司凭借十年行业沉淀与平台官方资源加持,打破传统外贸获客壁垒,助力企业高效开拓全球市场,成为中小企业出海的可靠合作伙伴。