You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何实现Python只读属性通用Descriptor?含列表元素防修改

将@property只读逻辑重构为通用Descriptor并解决可变对象修改问题

一、解决Descriptor初始化报错问题

原Descriptor实现中,类内部初始化属性时会触发__set__方法抛出异常,核心原因是它没有区分外部赋值内部对私有属性的操作。我们可以调整Descriptor的设计,让它映射到实例的私有存储属性,类内部直接操作私有属性,外部通过Descriptor访问,完全复现@property的只读逻辑。

修改后的ReadOnly Descriptor实现:

class ReadOnly:
    def __set_name__(self, owner, name):
        # 将公开属性名映射为实例的私有存储属性(例如:n -> _n)
        self._storage_name = '_' + name

    def __get__(self, instance, owner):
        if instance is None:
            return self
        # 从实例的私有属性中取值
        return getattr(instance, self._storage_name)

    def __set__(self, instance, value):
        raise AttributeError(f"Cannot set readonly attribute '{self._storage_name[1:]}'")

    def __delete__(self, instance):
        raise AttributeError(f"Cannot delete readonly attribute '{self._storage_name[1:]}'")

重构后的类A:

class A:
    n = ReadOnly()
    top = ReadOnly()

    def __init__(self, n):
        # 直接操作私有属性,不会触发Descriptor的__set__方法
        self._n = n
        self._top = -1

    def increase(self):
        # 内部修改私有属性,正常执行
        self._top += 1

此时外部代码尝试a.n = 10会抛出AttributeError,但类内部可以自由修改self._nself._top,和@property的行为完全一致。

二、禁止只读属性中可变对象的修改

当只读属性指向列表、字典这类可变对象时,虽然不能重新赋值属性本身,但外部仍可修改对象内部元素。要解决这个问题,我们需要在Descriptor返回值时,将可变对象包装为不可变容器。

步骤1:实现不可变列表包装类

class ImmutableList:
    def __init__(self, data):
        self._data = list(data)

    # 仅实现读取相关方法
    def __getitem__(self, index):
        return self._data[index]

    def __len__(self):
        return len(self._data)

    def __iter__(self):
        return iter(self._data)

    def __repr__(self):
        return repr(self._data)

    # 重写所有修改方法,抛出异常阻止修改
    def __setitem__(self, index, value):
        raise TypeError("Cannot modify immutable list")

    def append(self, value):
        raise TypeError("Cannot modify immutable list")

    def extend(self, iterable):
        raise TypeError("Cannot modify immutable list")

    def insert(self, index, value):
        raise TypeError("Cannot modify immutable list")

    def remove(self, value):
        raise TypeError("Cannot modify immutable list")

    def pop(self, index=-1):
        raise TypeError("Cannot modify immutable list")

    def clear(self):
        raise TypeError("Cannot modify immutable list")

    def sort(self, key=None, reverse=False):
        raise TypeError("Cannot modify immutable list")

    def reverse(self):
        raise TypeError("Cannot modify immutable list")

步骤2:升级ReadOnly Descriptor支持转换函数

给Descriptor添加可选的converter参数,用于在返回值时将可变对象转换为不可变包装:

class ReadOnly:
    def __init__(self, converter=None):
        self._converter = converter

    def __set_name__(self, owner, name):
        self._storage_name = '_' + name

    def __get__(self, instance, owner):
        if instance is None:
            return self
        value = getattr(instance, self._storage_name)
        # 如果有转换函数,返回转换后的不可变对象
        if self._converter is not None:
            return self._converter(value)
        return value

    def __set__(self, instance, value):
        raise AttributeError(f"Cannot set readonly attribute '{self._storage_name[1:]}'")

    def __delete__(self, instance):
        raise AttributeError(f"Cannot delete readonly attribute '{self._storage_name[1:]}'")

重构后的Stack类:

class Stack:
    # 对S属性使用ImmutableList转换
    S = ReadOnly(converter=ImmutableList)
    n = ReadOnly()
    top = ReadOnly()

    def __init__(self, n):
        self._S = [None] * n
        self._n = n
        self._top = -1

    def push(self, value):
        if self._top >= self._n - 1:
            raise IndexError("Stack overflow")
        self._top += 1
        # 内部直接修改私有列表,不受影响
        self._S[self._top] = value

现在外部代码尝试s.S[3] = 3s.S.append(5)都会抛出TypeError,而类内部可以正常修改self._S,完全实现了属性的只读(包括内部元素不可修改)。


内容的提问来源于stack exchange,提问作者Yehui He

火山引擎 最新活动