Python 如何实现“只读属性”但允许在 init 里赋值

Python实现只读属性的核心思路是用私有属性存储值、property提供只读访问,并在__init__中直接赋值私有变量;推荐property+私有属性,可选__slots__增强防护或描述符复用。

Python 中实现“只读属性”但允许在 __init__ 中赋值,核心思路是:**用私有属性存储值,通过 property 提供只读访问,并在初始化阶段绕过 property 直接写私有变量**。这样既保证实例创建后无法修改,又保留构造时的灵活性。

用 property + 私有属性(推荐)

这是最清晰、最 Pythonic 的方式。关键在于:property 的 setter 不定义或抛出异常,而 __init__ 直接给底层私有属性(如 self._x)赋值

示例:

class Person:
    def __init__(self, name):
        self._name = name  # ✅ 允许在 init 中直接赋值
@property
def name(self):
    return self._name

# ❌ 不定义 setter,或显式禁止
@name.setter
def name(self, value):
    raise AttributeError("name is read-only")

使用效果:

  • p = Person("Alice") → 成功
  • p.name → 返回

    "Alice"
  • p.name = "Bob" → 抛出 AttributeError

用 __slots__ + property(增强防护)

如果想进一步防止用户通过动态设置 p._name = ... 绕过限制,可结合 __slots__ 封锁实例字典:

class Person:
    __slots__ = ("_name",)  # 只允许 _name 属性
def __init__(self, name):
    self._name = name

@property
def name(self):
    return self._name

此时:p._name = "new" 仍可行(因为 __slots__ 不限制私有属性写入),但 p.age = 10 会报错,且无法新增任意属性干扰逻辑。

用描述符(适合复用场景)

若多个类都需要类似只读字段,可封装成描述符:

class ReadOnly:
    def __init__(self, default=None):
        self.default = default
        self.name = None  # 后续由 __set_name__ 填充
def __set_name__(self, owner, name):
    self.name = f"_{name}"

def __get__(self, obj, objtype=None):
    if obj is None:
        return self
    return getattr(obj, self.name, self.default)

def __set__(self, obj, value):
    if not hasattr(obj, self.name):  # 仅允许第一次设置(即 init 阶段)
        setattr(obj, self.name, value)
    else:
        raise AttributeError(f"{self.name[1:]} is read-only")

class Person: name = ReadOnly()

def __init__(self, name):
    self.name = name  # ✅ 第一次赋值成功

注意:该描述符依赖“首次赋值”判断,适用于简单场景;复杂逻辑建议回归 property 方案。

不推荐的做法:重写 __setattr__

虽然可以拦截所有属性赋值,但容易误伤(比如影响 self._name__init__ 中的设置),需额外维护白名单,代码易出错、难维护,一般不建议。