支持属性访问嵌套字典的自定义Map类print输出与__dict__值不一致问题
问题根因
你自定义的Map类同时维护了两套独立的存储,二者没有同步导致数据不一致:
- 父类
dict的原生存储:打印data实例时输出的是这部分的内容 - 实例自身的
__dict__存储:你通过data.df访问到的是这部分的内容
具体到你的代码逻辑,__setitem__方法执行时,先把原始的普通字典存入父类dict,再把转换后的Map实例存入__dict__,二者从赋值开始就是完全独立的两个对象。后续你修改data.df[time]时,改的是__dict__里的Map实例,父类dict里存储的原始普通字典完全没有被修改,因此打印实例时看到的还是旧值。
另外你的__getattr__方法存在逻辑错误:先调用super(Map, self).__getattr__(attr),但父类dict本身没有实现__getattr__方法,属性不存在时会直接抛出AttributeError,根本不会执行后续的return self.get(attr)逻辑。
自定义类修复方案
调整__setitem__的执行顺序,保证父类dict和__dict__中存储的是同一个对象,同时修复__getattr__的错误逻辑即可:
class Map(dict): """ 支持点号访问的字典实现 Example: m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer']) """ def __init__(self, *args, **kwargs): super(Map, self).__init__(*args, **kwargs) for arg in args: if isinstance(arg, dict): for k, v in arg.items(): self[k] = v if kwargs: for k, v in kwargs.items(): self[k] = v def __getattr__(self, attr): try: return self[attr] except KeyError: raise AttributeError(f"'Map' object has no attribute '{attr}'") def __setattr__(self, key, value): self.__setitem__(key, value) def __setitem__(self, key, value): # 先转换字典为Map实例,保证两处存储的是同一个对象 if isinstance(value, dict): value = Map(value) super(Map, self).__setitem__(key, value) self.__dict__[key] = value def __delattr__(self, item): self.__delitem__(item) def __delitem__(self, key): super(Map, self).__delitem__(key) del self.__dict__[key]
修复后你原来的测试代码可以得到预期输出。
更推荐的现成数据结构
不需要自己造轮子,有多个成熟的现成方案适配你的需求:
- 标准库SimpleNamespace:轻量无依赖,适合属性固定的简单场景
from types import SimpleNamespace # 嵌套也支持点号访问 data = SimpleNamespace( start_date = "2020-01-01", finish_date = "2021-01-01", name = "Type A", df = SimpleNamespace(yearly = 0, monthly = 0, weekly = 0) ) # 直接用data.df.yearly访问和修改 - dataclass:Python 3.7+内置,支持自定义方法、默认值等,比SimpleNamespace灵活性更高
from dataclasses import dataclass @dataclass class DfData: yearly: int monthly: int weekly: int @dataclass class ProjectData: start_date: str finish_date: str name: str df: DfData data = ProjectData( start_date = "2020-01-01", finish_date = "2021-01-01", name = "Type A", df = DfData(yearly=0, monthly=0, weekly=0) ) - Pydantic BaseModel:适合工业级项目,支持类型校验、自动序列化、数据转换等功能,即使存储pd.DataFrame也可以通过自定义字段类型支持
from pydantic import BaseModel class DfData(BaseModel): yearly: int monthly: int weekly: int class ProjectData(BaseModel): start_date: str finish_date: str name: str df: DfData data = ProjectData( start_date = "2020-01-01", finish_date = "2021-01-01", name = "Type A", df = {"yearly":0, "monthly":0, "weekly":0} )
内容的提问来源于stack exchange,提问作者Nickkon
相关产品推荐
相关产品推荐

