You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何封装Downloader类的私有可变DownloadItem,以不可变形式公开属性?

解决方案

要实现仅允许Downloader修改DownloadItem,外部只读访问的需求,核心是通过代理模式实现一个真正的不可变包装器,禁止外部对包装后的对象进行赋值操作,同时避免编写大量样板代码。

1. 改进不可变包装器

重写包装器的__setattr__和__delattr__方法,直接拦截所有外部赋值/删除操作并抛出异常,同时确保内部能正确持有底层DownloadItem对象:

class ImmutableWrapper:
    def __init__(self, obj):
        # 直接操作__dict__绕过__setattr__,避免初始化时触发异常
        self.__dict__['_obj'] = obj

    def __getattr__(self, name):
        # 转发属性获取请求到底层对象
        return getattr(self._obj, name)

    def __setattr__(self, name, value):
        # 禁止任何外部赋值操作
        raise AttributeError("无法修改只读的下载项对象")

    def __delattr__(self, name):
        # 禁止任何外部删除属性操作
        raise AttributeError("无法删除只读下载项的属性")

    # 可选:转发字符串表示方法,让包装器的打印更友好
    def __str__(self):
        return str(self._obj)

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

2. 调整Downloader的返回逻辑

在get_download_by_id方法中返回包装后的DownloadItem,而不是原始对象:

class Downloader:
    def __init__(self):
        # 用字典存储下载项,key为唯一ID,value为原始DownloadItem
        self._download_queue = {}

    def download(self, url, start_immediate=False):
        # 创建原始DownloadItem(仅Downloader可修改)
        item = DownloadItem(url)
        unique_id = id(item)  # 或用uuid生成更可靠的唯一ID
        self._download_queue[unique_id] = item
        if start_immediate:
            self.start_download(unique_id)
        return unique_id

    def start_download(self, unique_id):
        # 模拟后台线程更新DownloadItem元数据的逻辑
        import threading
        def update_metadata():
            item = self._download_queue[unique_id]
            # 模拟从服务器获取文件大小
            item.filesize = 1024
            item.status = "downloading"
        threading.Thread(target=update_metadata).start()

    def get_download_by_id(self, unique_id):
        item = self._download_queue.get(unique_id)
        if item:
            # 返回包装后的只读对象
            return ImmutableWrapper(item)
        return None

3. 原始DownloadItem类

保持DownloadItem的可变性,仅在Downloader内部修改:

class DownloadItem:
    def __init__(self, url):
        self.url = url
        self.filesize = -1  # 初始未知
        self.status = "pending"
        self.start_time = None
        # 其他需要的属性...

    def __repr__(self):
        return f"<DownloadItem url: {self.url} filesize: {self.filesize}>"

测试验证

# 初始化下载器
downloader = Downloader()
# 添加下载任务
unique_id = downloader.download(url='https://path/to/resource', start_immediate=False)
print(downloader._download_queue[unique_id])  # <DownloadItem url: https://path/to/resource filesize: -1>
# 启动下载(后台更新元数据)
downloader.start_download(unique_id)
# 等待线程执行
import time
time.sleep(0.1)
# 获取只读的下载项对象
download_item = downloader.get_download_by_id(unique_id)
print(download_item.filesize)  # 1024
# 尝试修改属性,触发异常
download_item.filesize = 2  # AttributeError: 无法修改只读的下载项对象

设计思路说明

  • 代理模式:ImmutableWrapper作为DownloadItem的代理,控制外部访问权限,仅允许读取操作,解耦了Downloader和DownloadItem的耦合度。
  • 开闭原则:后续扩展DownloadItem的字段时,无需修改Downloader或ImmutableWrapper的代码,自动支持新属性的只读访问。
  • 明确的错误反馈:直接抛出AttributeError,避免外部误操作导致的属性混淆(如原包装器中赋值仅作用于包装器本身的问题)。

内容的提问来源于stack exchange,提问作者César

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 17:30:58