0

0

如何让属性成为只读描述符但允许 init 赋值

舞夢輝影

舞夢輝影

发布时间:2026-01-26 17:27:10

|

197人浏览过

|

来源于php中文网

原创

结论:@property 的 setter 为空时,__init__ 中无法赋值,因属性访问走 descriptor 协议;需自定义描述符(如 InitOnlyDescriptor),利用 set_name 动态生成私有名,并在 set 中用哨兵值或 hasattr 检查首次赋值,确保 init 可写、之后只读。

如何让属性成为只读描述符但允许 init 赋值

Python 中 @property 无法在 __init__ 中赋值?用 __set_name__ + 实例字典绕过

直接说结论:标准 @property 的 setter 被设为空时,__init__ 里也不能赋值——因为属性访问走的是 descriptor 协议,不是普通实例属性。真要“只读但 init 可写”,得自定义描述符,且在初始化阶段绕过只读逻辑。

常见错误是这么写:

class Person:
    def __init__(self, name):
        self._name = name  # ✅ 看似 OK,但 _name 是普通属性,不触发 property
    @property
    def name(self):
        return self._name
    @name.setter
    def name(self, _):
        raise AttributeError("name is read-only")

问题在于:self._name 完全脱离了描述符控制,外部仍可直接改 obj._name,且没做任何初始化校验。

  • 必须把值存在实例字典里(如 obj.__dict__['name']),而非用下划线字段模拟
  • 描述符需实现 __set_name__ 获取属性名,并在 __set__ 中区分是否为首次赋值(即 __init__ 阶段)
  • 不能依赖 hasattr(obj, 'name') 判定是否已初始化——因为 __get__ 可能返回默认值,造成误判
  • 推荐在 __set__ 中检查 value is NOT_SET(用哨兵对象),而不是检查 key 是否在 __dict__ 里——避免和 __slots__ 冲突

__set_name__ 和哨兵值实现真正的 init-only 写入

核心是让描述符知道“这是第一次设置”,且只允许在 __init__ 期间发生。关键不在于时间点,而在于是否已存在该键。

黑点工具
黑点工具

在线工具导航网站,免费使用无需注册,快速使用无门槛。

下载
class InitOnlyDescriptor:
    NOT_SET = object()
    def __set_name__(self, owner, name):
        self.name = name
        self.private_name = f'_{name}'
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if not hasattr(obj, self.private_name):
            raise AttributeError(f"'{type(obj).__name__}' object has no attribute '{self.name}'")
        return getattr(obj, self.private_name)
    def __set__(self, obj, value):
        if hasattr(obj, self.private_name):
            raise AttributeError(f"{self.name} is read-only")
        setattr(obj, self.private_name, value)

class Person: name = InitOnlyDescriptor() def init(self, name): self.name = name # ✅ 触发 set,且此时 private_name 不存在

  • self.name = name__init__ 中合法,因为 __set__ 检查的是 hasattr(obj, private_name),初始为 False
  • 之后再写 person.name = "new" 就会抛 AttributeError
  • 外部无法通过 person._name 绕过——因为真实值存的是 _name(由 __set_name__ 动态生成),不是硬编码的字符串
  • 如果类用了 __slots__,需确保 private_name 在 slots 列表中,否则 setattr 会失败

为什么不用 __post_init__dataclassinit=False

那是另一条路,但不符合“init 中赋值”这个前提。比如 dataclass(init=False) 要求你手动写 __init__,且字段本身不参与构造参数,等于放弃语言级的参数绑定。

  • dataclass(frozen=True) 是只读,但所有字段都不可变,连 init 都不允许改
  • field(default_factory=...) 无法接收 __init__ 参数,只能固定值或闭包
  • __post_init__ 里赋值仍是普通属性写入,不经过 descriptor,失去类型/范围校验能力
  • 如果你需要在 init 时做转换(比如把字符串转为 datetime),必须走描述符的 __set__ 才能统一拦截

容易被忽略的边界:继承、多继承和 __slots__

自定义描述符在复杂继承链里可能被覆盖或跳过,尤其当父类也定义同名属性时。__set_name__ 虽然在类创建时调用,但若子类重定义该属性,会重新触发一次,导致 private_name 被覆盖。

  • 若父类有 name = InitOnlyDescriptor(),子类又写一遍,那父类的 descriptor 就失效了
  • 使用 __slots__ 时,必须显式包含 private_name 字符串,例如:__slots__ = ('_name',),否则 setattrAttributeError
  • 不要在 __get__ 中用 getattr(obj, self.name, ...)——这会无限递归,因为 self.name 是公开属性名,访问它又触发 __get__

真正难的不是写一个只读属性,而是让“只读”和“init 可写”在 descriptor 协议层面严格共存。大部分坑都出在对 set 触发时机和实例状态判断的理解偏差上。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
python开发工具
python开发工具

php中文网为大家提供各种python开发工具,好的开发工具,可帮助开发者攻克编程学习中的基础障碍,理解每一行源代码在程序执行时在计算机中的过程。php中文网还为大家带来python相关课程以及相关文章等内容,供大家免费下载使用。

778

2023.06.15

python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

686

2023.07.20

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

769

2023.07.25

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

740

2023.07.31

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

1445

2023.08.03

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

571

2023.08.04

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

581

2023.08.04

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

752

2023.08.11

拼多多赚钱的5种方法 拼多多赚钱的5种方法
拼多多赚钱的5种方法 拼多多赚钱的5种方法

在拼多多上赚钱主要可以通过无货源模式一件代发、精细化运营特色店铺、参与官方高流量活动、利用拼团机制社交裂变,以及成为多多进宝推广员这5种方法实现。核心策略在于通过低成本、高效率的供应链管理与营销,利用平台社交电商红利实现盈利。

31

2026.01.26

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.2万人学习

Django 教程
Django 教程

共28课时 | 3.5万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号