需实现__getitem__支持方括号访问,并结合__getattr__、__getattribute__、继承SimpleNamespace或封装dict四种方式之一支持点号访问,各方法需规避递归并正确处理属性与键名映射。
![python 如何让一个类同时支持 [] 和 . 两种访问方式](https://img.php.cn/upload/article/001/242/473/176864048842021.png)
如果希望一个 Python 类既能通过方括号语法(如 obj[key])访问元素,又能通过点号语法(如 obj.key)访问属性,则需分别实现特定的魔术方法并合理设计属性访问机制。以下是实现该目标的多种方法:
一、实现 __getitem__ 和 __getattr__ 组合
该方法利用 __getitem__ 支持 obj[key] 访问,同时用 __getattr__ 拦截未定义的属性访问,将其转为键查找。适用于数据以字典形式存储且允许动态键名的场景。
1、在类中定义 __init__ 方法,将初始数据存入内部字典(如 self._data = {} 或 self._data = dict(kwargs))。
2、实现 __getitem__(self, key) 方法,直接返回 self._data[key]。
立即学习“Python免费学习笔记(深入)”;
3、实现 __getattr__(self, name) 方法,在该方法中检查 name 是否为 self._data 的键;若是,返回 self._data[name];否则抛出 AttributeError。
4、为避免 __getattr__ 干扰已有属性,确保所有预设属性(如 _data)在 __init__ 中已显式初始化。
二、使用 __getattribute__ 与 __getitem__ 协同
该方法比 __getattr__ 更底层,可统一拦截所有属性访问,包括已存在的属性。需谨慎处理,防止无限递归,适合对访问逻辑有精细控制需求的情况。
1、在 __getattribute__ 方法内部,先尝试按常规方式获取属性(调用 super().__getattribute__(name))。
2、若捕获 AttributeError,再检查该 name 是否存在于内部数据字典中。
3、若存在,返回字典对应值;否则重新抛出原始 AttributeError。
4、在 __getitem__ 中保持对 self._data[key] 的直接返回。
5、在 __getattribute__ 开头添加保护逻辑:若 name 为 '__dict__'、'_data' 等关键名称,直接调用 super() 返回,避免因访问 self._data 触发 __getattribute__ 造成递归调用。
三、继承 types.SimpleNamespace 并混入 __getitem__
借助 SimpleNamespace 提供的动态属性赋值能力,减少手动管理 __getattr__ 的复杂度,再补充 __getitem__ 实现方括号访问。
1、定义类继承自 types.SimpleNamespace。
2、在 __init__ 中调用 super().__init__(**kwargs),使传入参数自动转为属性。
3、实现 __getitem__(self, key),检查 self.__dict__ 是否包含 key;若存在,返回 getattr(self, key);否则抛出 KeyError。
4、为支持点号设置(obj.key = value)和方括号设置(obj[key] = value),还需实现 __setitem__,并在其中调用 setattr(self, key, value)。
5、注意:SimpleNamespace 不支持 __getattr__,因此仅靠它无法 fallback 到字典键查找,必须依赖 __dict__ 查找逻辑。
四、封装 dict 并重载 __getattribute__ 实现透明代理
将类设计为 dict 的代理对象,对外表现接近原生 dict,同时允许点号访问键名对应的值。适用于需要保留 dict 接口又增强可读性的场景。
1、在 __init__ 中初始化 self._dict = dict(kwargs) 或传入的 dict 实例。
2、实现 __getitem__ 和 __setitem__,全部委托给 self._dict。
3、重写 __getattribute__:对非魔术属性名(不以下划线开头),尝试从 self._dict 获取;若失败,再调用 super()。
4、在 __getattribute__ 中排除对 '_dict'、'__dict__'、'__getitem__' 等名称的代理,否则会导致属性访问链断裂或无限递归。
5、提供 __contains__、keys()、values() 等方法,均代理至 self._dict,以维持接口一致性。










