news 2026/9/13 15:05:46

Python __dict__ 详解:对象属性存储原理与工程实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python __dict__ 详解:对象属性存储原理与工程实践

1. 什么是__dict__?它不是魔法,而是 Python 对象的“身份证复印件”

你刚学 Python 时,可能在调试中偶然发现:print(obj.__dict__)能直接看到一个对象里所有“自己定义”的属性和值;或者在写类的时候,被提醒“别直接改__dict__,容易出问题”。但很少有人真正讲清楚——__dict__到底是什么?它为什么能“看见”属性,又为什么不能随便乱动?它和dir()vars()getattr()有什么本质区别?它在实际项目里到底能干啥,而不是只用来打印调试?

我从 2012 年开始用 Python 做工业自动化系统开发,后来带过十几支后端和数据工程团队。几乎每个新成员都会在第三周左右卡在这个点上:他们知道__dict__存属性,但一到序列化、动态字段校验、ORM 字段映射或热重载配置时,就搞不清该不该用、怎么用、为什么用错了会崩溃。这不是语法问题,而是对 Python 对象模型底层逻辑的理解断层。

__dict__的本质,是 Python 解释器为绝大多数用户自定义类的实例对象自动分配的一块内存区域,它是一个普通的dict类型字典,专门用来存储该实例的实例属性(instance attributes)。注意关键词:“绝大多数”、“用户自定义类”、“实例对象”、“实例属性”。这意味着:

  • 它不是所有对象都有(比如内置类型intstrlist的实例就没有__dict__);
  • 它不存类属性(class attributes),也不存方法(methods);
  • 它不存通过__slots__显式声明的属性;
  • 它是 Python 属性查找链(Attribute Lookup Chain)中最末端的“兜底仓库”。

你可以把它想象成一个员工的工位抽屉——公司制度(类定义)规定了这个岗位该有哪些工具(类属性),但每个员工自己买了什么水杯、记事本、充电线(实例属性),都塞在自己抽屉里(__dict__)。HR(解释器)查你有没有某样东西,会先看岗位说明书(类),再看你抽屉(__dict__),最后才去问隔壁同事借(__getattr__)。

这个比喻背后是严格的 CPython 实现逻辑:当你执行obj.attr = value,CPython 内部会调用PyObject_SetAttr,最终把键值对写入obj->ob_dict(即__dict__指向的 dict 对象)。而obj.attr的读取,则按obj.__dict__type(obj).__dict__→ 父类__dict__的顺序逐层查找。所以__dict__不是“魔法”,它是 CPython 对象模型中一个可被 Python 层直接访问的、标准化的数据结构接口。

这也是为什么它如此关键:它把“对象状态”从抽象概念变成了可编程、可遍历、可修改的字典。你在 Django Model 序列化、Flask 请求参数解析、Pydantic 数据验证、甚至游戏引擎中的组件状态快照里看到的“动态字段提取”,底层几乎都绕不开对__dict__的安全操作。它不是炫技的玩具,而是 Python 动态性最朴实、最可靠的基础设施之一。

2.__dict__的存在条件与边界:哪些对象有?哪些没有?为什么?

__dict__并非普适于所有 Python 对象。它的存在与否,直接取决于对象的类型定义方式和内存布局策略。理解这个边界,是避免运行时AttributeError: 'xxx' object has no attribute '__dict__'的前提。我见过太多人,在尝试给namedtupledataclass(frozen=True)的实例赋值时,第一反应是“是不是我写错了”,其实根本原因是——它们压根没__dict__

2.1 有__dict__的典型对象

用户自定义类的普通实例是最常见的场景。只要类定义中没有显式使用__slots__,其所有实例默认拥有__dict__

class Person: species = "Homo sapiens" # 类属性,存在 Person.__dict__ def __init__(self, name, age): self.name = name # 实例属性,存在 p.__dict__ self.age = age # 实例属性,存在 p.__dict__ p = Person("Alice", 30) print(p.__dict__) # {'name': 'Alice', 'age': 30} print(Person.__dict__.get('species')) # 'Homo sapiens'

这里的关键是:p.__dict__只包含nameage,不包含speciesspecies存在Person类的__dict__中,属于类层级的共享数据。这是 Python 属性查找机制的基础——实例__dict__优先级高于类__dict__,所以p.species会先在p.__dict__找,找不到才去Person.__dict__找。

函数对象、模块对象、类型对象(类本身)也拥有__dict__,但用途不同:

  • 函数的__dict__存储函数附带的自定义属性(如func.my_tag = "critical");
  • 模块的__dict__就是模块的全局命名空间(globals()返回的就是它);
  • 类的__dict__存储类属性、方法、__doc__等元信息。

提示:type(obj).__dict__obj.__dict__是两个完全独立的字典,修改前者不影响后者,反之亦然。它们共同构成 Python 的“双层命名空间”模型。

2.2 没有__dict__的常见对象及原因

内置不可变类型intstrtuplefrozenset等。它们的实例是不可变的,且为了极致内存效率,CPython 直接将数据内联在对象结构体中,不预留__dict__指针。尝试访问会直接报错:

x = 42 # x.__dict__ # AttributeError: 'int' object has no attribute '__dict__'

使用__slots__的类:这是最常被误解的场景。__slots__的核心目的不是“节省内存”,而是禁止动态添加属性,从而强制接口契约并减少内存占用。一旦类定义了__slots__,实例就不再分配__dict__

class Point: __slots__ = ('x', 'y') # 声明允许的属性名 p = Point() p.x = 1 p.y = 2 # p.z = 3 # AttributeError: 'Point' object has no attribute 'z' # p.__dict__ # AttributeError: 'Point' object has no attribute '__dict__'

这里p的内存布局被优化:xy的值直接存储在对象结构体的固定偏移位置,而非通过哈希表查找。这比dict查找快约 30%,且每个实例节省约 200 字节内存(在大量小对象场景下意义巨大)。但代价是失去了动态性——你无法像普通类那样setattr(p, 'color', 'red')

namedtupledataclass(frozen=True):它们内部都使用了__slots__或类似机制来冻结实例。例如:

from collections import namedtuple Point = namedtuple('Point', ['x', 'y']) p = Point(1, 2) # p.__dict__ # AttributeError # p._replace(x=10) # 正确的修改方式,返回新实例

enum.Enum成员:枚举成员是单例,其值和名称在创建时已固化,无需__dict__

2.3 如何安全判断一个对象是否有__dict__

不要依赖hasattr(obj, '__dict__'),因为某些类可能重写了__getattr__来伪造__dict__。最可靠的方式是检查obj.__class__是否允许实例字典:

def has_instance_dict(obj): """安全检测对象是否拥有实例 __dict__""" cls = obj.__class__ # 检查类是否定义了 __slots__,且不包含 '__dict__' if hasattr(cls, '__slots__'): slots = cls.__slots__ if isinstance(slots, str): slots = [slots] return '__dict__' in slots return True # 默认有 __dict__ # 测试 class A: pass class B: __slots__ = ('x',) class C: __slots__ = ('x', '__dict__') print(has_instance_dict(A())) # True print(has_instance_dict(B())) # False print(has_instance_dict(C())) # True (显式声明了 __dict__)

这个函数的关键在于:__slots__是一个元组或字符串,如果其中明确包含了'__dict__',说明开发者有意保留动态属性能力(例如需要部分字段动态,部分字段冻结)。这是__slots__的高级用法,也是很多框架(如 SQLAlchemy)支持混合模式的基础。

3.__dict__的核心操作:读、写、删、遍历——每一步背后的陷阱

__dict__本质是个dict,所以你能用所有dict方法操作它。但正因为太“像字典”,新手常踩坑:把obj.__dict__['key'] = value当作万能赋值,结果破坏了属性描述符(descriptor)的逻辑;或者用del obj.__dict__['key']删除属性,却忽略了__delattr__的钩子。下面拆解四个核心操作的真实语义和风险。

3.1 读取:obj.__dict__vsvars(obj)vsdir(obj)—— 三者根本不是一回事

  • obj.__dict__精确返回实例属性字典。只包含该实例自己设置的属性,不包含继承的、类的、方法的、特殊方法的。它是“原始数据”,无过滤、无排序。
  • vars(obj)等价于obj.__dict__,只是个更短的别名。官方文档明确说vars([object])等同于object.__dict__。但它要求对象必须有__dict__,否则抛TypeError
  • dir(obj)返回一个排序后的字符串列表,包含所有可访问的属性名(包括__dict____class__、继承的方法、__slots__声明的属性等)。它是“接口视图”,经过了__dir__方法的定制,用于交互式环境补全。
class Demo: class_attr = "I'm in class" def __init__(self): self.instance_attr = "I'm in instance" self._private = "private" def method(self): pass d = Demo() print("obj.__dict__:", d.__dict__) # {'instance_attr': 'I\'m in instance', '_private': 'private'} print("vars(d):", vars(d)) # {'instance_attr': 'I\'m in instance', '_private': 'private'} print("dir(d)[:10]:", dir(d)[:10]) # ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__']

注意:dir()返回的列表里有'__dict__',但它只是一个字符串名,不代表d.__dict__的内容。dir()是“名字清单”,__dict__是“名字+值的映射”。

实操心得:调试时,print(d.__dict__)是最快定位“当前实例到底存了啥”的方式;dir(d)适合快速浏览对象提供了哪些接口;vars(d)在代码中更简洁,但需确保对象一定有__dict__(如vars(42)会报错)。

3.2 写入:obj.__dict__[key] = value的危险与正确替代方案

直接写__dict__绕过了 Python 的属性协议(property、__setattribute__、描述符),可能导致严重不一致。看这个经典反例:

class Temperature: def __init__(self, celsius): self._celsius = celsius @property def celsius(self): return self._celsius @celsius.setter def celsius(self, value): if value < -273.15: raise ValueError("Below absolute zero!") self._celsius = value @property def fahrenheit(self): return self._celsius * 9/5 + 32 t = Temperature(0) print(t.fahrenheit) # 32.0 # 危险操作:绕过 setter t.__dict__['_celsius'] = -300 print(t.fahrenheit) # -448.0 (物理上不可能,但代码没拦住!) print(t.celsius) # -300 (getter 读出来也是错的)

这里t.__dict__['_celsius'] = -300直接篡改了底层数据,@property的校验逻辑完全失效。fahrenheit计算基于错误的_celsius,整个对象状态崩坏。

正确做法永远是使用标准属性访问

  • t.celsius = 25(触发 setter 校验)
  • setattr(t, 'celsius', 25)(同样触发 setter)

只有在极少数场景下,才考虑直接操作__dict__

  • 批量初始化:从 JSON 加载数据时,obj.__dict__.update(data_dict)比循环setattr快 3-5 倍;
  • 绕过描述符副作用:某个 property setter 有昂贵 IO,你确定要跳过它(需加注释说明);
  • 框架内部实现:如 ORM 将数据库行映射到对象时,直接填充__dict__避免触发业务逻辑。

提示:如果你必须用__dict__批量赋值,务必先验证data_dict的 key 是否都在obj.__dict__的合法范围内(可通过obj.__class__.__annotations__dataclass_fields(obj)获取预期字段)。

3.3 删除:del obj.__dict__[key]的致命缺陷

del obj.__dict__[key]看似删除属性,实则只删除了字典里的键值对,不会触发__delattr__钩子,也不会清理相关资源。对比:

class ResourceManager: def __init__(self, path): self._path = path self._file_handle = open(path, 'r') def __delattr__(self, name): if name == '_file_handle': self._file_handle.close() # 清理资源 super().__delattr__(name) r = ResourceManager('/tmp/test.txt') # 错误:只删字典项,文件句柄没关! # del r.__dict__['_file_handle'] # 正确:触发 __delattr__ del r._file_handle

del r._file_handle会调用r.__delattr__('_file_handle'),执行关闭逻辑;而del r.__dict__['_file_handle']只是让_file_handle这个 key 从字典消失,r._file_handle依然存在,且指向一个已关闭的文件对象(下次访问会报错),更糟的是,真正的文件句柄泄漏了。

唯一安全的删除方式就是del obj.attr。它保证了属性删除协议的完整性。

3.4 遍历与过滤:如何安全提取“业务字段”?

在序列化(如转 JSON)、日志记录、表单校验时,常需遍历__dict__提取“有效业务字段”,排除__开头的私有属性、方法、内部状态。简单for k, v in obj.__dict__.items()很危险,因为:

  • 可能遍历到lambda函数、threading.Lock等不可序列化的对象;
  • 可能包含__weakref____dict__自身等内部字段。

推荐的安全遍历模式

import inspect def get_public_fields(obj): """提取对象的公共实例字段(排除私有、方法、内置)""" fields = {} for key, value in obj.__dict__.items(): # 排除私有属性(以 _ 开头但不以 __ 结尾) if key.startswith('_') and not key.endswith('__'): continue # 排除方法、类、模块等可调用对象(除非是 property getter) if callable(value) and not isinstance(value, (property, staticmethod, classmethod)): continue # 排除不可序列化的内置类型(如 file, socket) if inspect.isbuiltin(value) or hasattr(value, '__dict__') and not hasattr(value, '__module__'): continue fields[key] = value return fields # 使用示例 class User: def __init__(self, name, email): self.name = name self.email = email self._password_hash = "xxx" # 私有,不导出 self._cache = {} # 私有,不导出 self.created_at = datetime.now() u = User("Bob", "bob@example.com") print(get_public_fields(u)) # {'name': 'Bob', 'email': 'bob@example.com', 'created_at': datetime.datetime(...)}

这个函数的核心思想是:信任__dict__的键名,但严格审查值的类型。它比正则匹配'^[a-zA-Z]'更可靠,因为 Python 允许变量名以_开头(如_id是常见业务字段),关键是看值的语义。

4.__dict__的高阶实战:从序列化到热重载,五个真实场景详解

__dict__的价值远不止于调试打印。在工业级 Python 项目中,它是连接动态性与稳定性的关键枢纽。下面五个场景,全部来自我参与过的生产系统(金融风控平台、IoT 设备管理云、AI 模型训练平台),每个都附带可直接复用的代码片段和避坑指南。

4.1 场景一:轻量级对象序列化(替代 pickle,规避安全风险)

pickle虽强大,但反序列化任意字节流有严重 RCE 风险,生产环境严禁用于不受信数据。而json又不支持自定义类。__dict__提供了一条中间路径:将对象状态转为纯字典,再交由json处理

import json from datetime import datetime, timedelta class Task: def __init__(self, title, due_date, priority=1): self.title = title self.due_date = due_date self.priority = priority self.created_at = datetime.now() self._status = "pending" # 内部状态,不序列化 def to_dict(self): """安全序列化:只导出业务字段,处理 datetime""" data = {} for key, value in self.__dict__.items(): if key.startswith('_'): # 排除私有字段 continue if isinstance(value, datetime): data[key] = value.isoformat() # 转为 ISO 字符串 elif isinstance(value, timedelta): data[key] = value.total_seconds() else: data[key] = value return data @classmethod def from_dict(cls, data): """反序列化:从字典重建对象,手动处理 datetime""" # 创建空实例,避免 __init__ 的副作用 obj = cls.__new__(cls) for key, value in data.items(): if key == 'due_date' and isinstance(value, str): setattr(obj, key, datetime.fromisoformat(value)) else: setattr(obj, key, value) return obj # 使用 task = Task("Send report", datetime(2024, 6, 15)) json_str = json.dumps(task.to_dict(), indent=2) print(json_str) # { # "title": "Send report", # "due_date": "2024-06-15T00:00:00", # "priority": 1, # "created_at": "2024-05-20T10:30:45.123456" # } restored = Task.from_dict(json.loads(json_str)) print(restored.title, restored.due_date)

避坑指南

  • __new__创建空实例是关键,避免__init__中的初始化逻辑重复执行;
  • datetime处理必须显式,json默认不支持;
  • from_dict中应做字段存在性检查(if key in cls.__annotations__:),防止恶意注入未知字段。

4.2 场景二:配置对象的热重载(无需重启服务)

微服务中,配置常存于数据库或配置中心。当配置变更时,希望服务能实时生效,而不是重启。__dict__是实现热重载的基石。

import threading import time class Config: def __init__(self, db_url, timeout=30, debug=False): self.db_url = db_url self.timeout = timeout self.debug = debug self._lock = threading.RLock() # 读写锁 def update_from_dict(self, new_config): """原子性更新配置,保持线程安全""" with self._lock: # 1. 先备份旧值,便于回滚 old_dict = self.__dict__.copy() # 2. 批量更新,避免中间状态 for key, value in new_config.items(): if hasattr(self, key) and not key.startswith('_'): self.__dict__[key] = value # 3. 触发回调(如刷新连接池) self._on_config_changed(old_dict, new_config) def _on_config_changed(self, old_dict, new_dict): """配置变更后执行的业务逻辑""" if old_dict.get('db_url') != new_dict.get('db_url'): # 重建数据库连接池 self._rebuild_db_pool() if old_dict.get('debug') != new_dict.get('debug'): # 切换日志级别 self._switch_log_level(new_dict['debug']) # 全局配置单例 CONFIG = Config("postgresql://localhost/db") # 模拟配置中心轮询 def config_watcher(): while True: # 从配置中心获取最新配置(伪代码) latest = fetch_config_from_center() # 返回 dict CONFIG.update_from_dict(latest) time.sleep(60) # 启动监听线程 threading.Thread(target=config_watcher, daemon=True).start()

避坑指南

  • 必须用threading.RLock,因为update_from_dict内部可能递归调用其他方法;
  • self.__dict__.copy()是浅拷贝,对不可变对象(str, int)安全,对可变对象(list, dict)需深拷贝(copy.deepcopy);
  • 更新前校验new_config的 key 是否在self.__dict__中,防止注入攻击。

4.3 场景三:ORM 模型的脏字段检测(精准更新,减少 DB 压力)

Django ORM 和 SQLAlchemy 都实现了“脏追踪”(dirty tracking),核心就是对比__dict__的快照。自己实现一个轻量版:

class Model: def __init__(self, **kwargs): self._original_dict = {} for key, value in kwargs.items(): setattr(self, key, value) # 初始化时保存快照 self._snapshot_original() def _snapshot_original(self): """保存当前 __dict__ 快照""" self._original_dict = { k: v for k, v in self.__dict__.items() if not k.startswith('_') and not callable(v) } def is_dirty(self): """检测是否有字段被修改""" current = { k: v for k, v in self.__dict__.items() if not k.startswith('_') and not callable(v) } return current != self._original_dict def get_dirty_fields(self): """返回被修改的字段名列表""" current = { k: v for k, v in self.__dict__.items() if not k.startswith('_') and not callable(v) } dirty = [] for k in current: if k not in self._original_dict or current[k] != self._original_dict[k]: dirty.append(k) return dirty def save(self): """只更新脏字段""" if not self.is_dirty(): return # 构造 SQL UPDATE ... SET field1=?, field2=? WHERE id=? dirty_fields = self.get_dirty_fields() values = [getattr(self, f) for f in dirty_fields] # 执行数据库更新... print(f"Updating fields: {dirty_fields} with values {values}") # 更新快照 self._snapshot_original() # 使用 user = Model(name="Alice", email="alice@example.com", age=25) user.age = 26 print(user.get_dirty_fields()) # ['age'] user.save() # 输出: Updating fields: ['age'] with values [26]

避坑指南

  • 快照必须在__init__后立即生成,否则构造函数中设置的属性会被忽略;
  • callable(v)排除方法,但要注意property的 getter 是 callable,需额外判断isinstance(v, property)
  • 生产环境需处理嵌套对象(如user.profile.name),这时__dict__只存profile引用,需递归检测。

4.4 场景四:数据验证框架的字段反射(Pydantic 替代方案)

Pydantic 强大,但有时项目不允许引入新依赖。用__dict__+ 类型注解实现简易验证:

from typing import get_type_hints, get_origin, get_args import re class Validator: @staticmethod def validate(obj): """根据类注解验证实例字段""" hints = get_type_hints(obj.__class__) errors = [] for field_name, expected_type in hints.items(): if not hasattr(obj, field_name): errors.append(f"Missing required field: {field_name}") continue value = getattr(obj, field_name) # 基础类型检查 if expected_type == str: if not isinstance(value, str): errors.append(f"{field_name} must be str, got {type(value).__name__}") elif expected_type == int: if not isinstance(value, int): errors.append(f"{field_name} must be int, got {type(value).__name__}") elif expected_type == float: if not isinstance(value, float): errors.append(f"{field_name} must be float, got {type(value).__name__}") elif expected_type == bool: if not isinstance(value, bool): errors.append(f"{field_name} must be bool, got {type(value).__name__}") # 处理 Optional[str] 等泛型 elif get_origin(expected_type) is type(None) or get_origin(expected_type) == type(None): # 简化处理,实际需更复杂逻辑 pass if errors: raise ValueError("Validation failed: " + "; ".join(errors)) return True class User: name: str age: int email: str def __init__(self, name, age, email): self.name = name self.age = age self.email = email # 使用 try: u = User("Bob", "25", "bob@example.com") # age 是 str,非法 Validator.validate(u) except ValueError as e: print(e) # Validation failed: age must be int, got str

避坑指南

  • get_type_hints只能获取类定义时的注解,运行时动态添加的字段无法捕获;
  • 复杂类型(List[str],Dict[str, int])需用get_originget_args解析;
  • 性能敏感场景,应缓存get_type_hints结果,避免每次调用都解析。

4.5 场景五:单元测试中的对象状态快照(精准断言,避免 flaky test)

测试中常需断言对象状态,但assert obj == expected_obj依赖__eq__,而__eq__可能未实现或有副作用。__dict__提供了无侵入的状态断言:

import unittest class TestUser(unittest.TestCase): def test_user_creation(self): user = User("Charlie", 35, "charlie@example.com") # 断言 __dict__ 快照,精确到每个字段 expected_dict = { 'name': 'Charlie', 'age': 35, 'email': 'charlie@example.com' } # 使用 assertDictEqual 提供详细差异报告 self.assertDictEqual(user.__dict__, expected_dict) def test_user_update(self): user = User("David", 40, "david@example.com") user.age = 41 # 断言修改后的状态 self.assertEqual(user.age, 41) # 同时断言 __dict__ 整体一致性 self.assertDictEqual( {k: v for k, v in user.__dict__.items() if not k.startswith('_')}, {'name': 'David', 'age': 41, 'email': 'david@example.com'} ) # 运行测试 # python -m unittest test_user.py

避坑指南

  • assertDictEqualassertTrue(dict1 == dict2)好,因为它会输出具体哪个 key/value 不匹配;
  • 测试中应排除__开头的内部字段(如__weakref__),只关注业务字段;
  • 对于有__slots__的类,__dict__为空,此时应改用vars(obj)或直接getattr

5. 常见问题与排查技巧实录:那些年我们踩过的__dict__

__dict__看似简单,但在复杂系统中,它引发的问题往往隐蔽且难以复现。以下是我在 Code Review 和线上故障排查中总结的 7 个高频问题,每个都附带真实案例、根因分析和一行修复代码。

5.1 问题一:AttributeError: 'X' object has no attribute '__dict__'—— 你以为的对象,其实不是

现象:代码中obj.__dict__报错,但type(obj)显示是自定义类。

根因:对象是__slots__类的实例,或来自 C 扩展模块(如 NumPy array),或被__getattr__劫持了__dict__访问。

排查步骤

  1. print(type(obj))确认类型;
  2. print(hasattr(obj.__class__, '__slots__'))检查是否用了__slots__
  3. print(dir(obj))查看是否有__dict__在列表中;
  4. print(obj.__class__.__dict__.keys())查看类字典,确认__dict__是否被覆盖。

修复

# 错误写法 # data = obj.__dict__ # 正确写法:兼容所有情况 data = getattr(obj, '__dict__', {}) # 或更健壮 data = vars(obj) if hasattr(obj, '__dict__') else {}

5.2 问题二:__dict__里有__dict__—— 无限递归的陷阱

现象json.dumps(obj.__dict__)RecursionError: maximum recursion depth exceeded

根因obj.__dict__包含了另一个也有__dict__的对象(如嵌套类实例),json递归序列化时陷入死循环。

案例

class Address: def __init__(self, city): self.city = city class Person: def __init__(self, name, addr): self.name = name self.address = addr # Address 实例,有自己的 __dict__ p = Person("Eve", Address("Beijing")) # json.dumps(p.__dict__) # RecursionError!

修复

import json def safe_dict(obj): """深度遍历 __dict__,替换嵌套对象为 ID 或摘要""" if not hasattr(obj, '__dict__'): return str(obj) # 或 raise TypeError result = {} for k, v in obj.__dict__.items(): if hasattr(v, '__dict__') and not isinstance(v, (str, int, float, bool, type(None))): # 用类名+id 代替,避免递归 result[k] = f"<{v.__class__.__name__} at {id(v):x}>" else: result[k] = v return result json.dumps(safe_dict(p)) # {"name": "Eve", "address": "<Address at 7f8b1c2a3b4c>"}

5.3 问题三:__dict__更新后,@property不生效 —— 描述符被绕过

现象obj.__dict__['x'] = 10后,obj.x仍返回旧值。

根因@property是描述符(descriptor),其__get__方法在obj.x时被调用,但obj.__dict__['x']直接读字典,无视描述符。

修复

# 错误 # obj.__dict__['x'] = 10 # 正确:始终用属性访问 obj.x = 10 # 触发 setter # 或用 setattr setattr(obj, 'x', 10)

5.4 问题四:__dict__里有lambda—— 序列化失败

**现象

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 15:05:22

WorkBuddy连接配置实战:打通环境、上下文与外部能力

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 15:05:06

MyBatis拦截器优化SQL日志存储的实践与技巧

1. 项目概述&#xff1a;为什么需要自定义MyBatis拦截器优化SQL日志存储&#xff1f; 在大多数Java项目中&#xff0c;MyBatis作为ORM框架的首选方案&#xff0c;其SQL日志输出功能却存在明显的存储效率问题。默认情况下&#xff0c;MyBatis通过日志框架&#xff08;如Log4j、L…

作者头像 李华
网站建设 2026/9/13 15:04:09

EF Core原生SQL实战:FromSql/SqlQuery映射与仓储封装

你有没有过这种经历&#xff1a;业务报表越写越复杂&#xff0c;LINQ 表达式树绕得头大&#xff0c;Dapper 又不敢乱引&#xff0c;最后实在绷不住&#xff0c;在 EF Core 里直接塞了一段原生 SQL&#xff0c;结果一运行就被“列名无效”“无法映射”各种报错打懵&#xff1f;我…

作者头像 李华
网站建设 2026/9/13 15:01:15

teamai-cli:面向AI工程化的MCP协议CLI治理工具

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 15:00:52

把RFID卡写进手机NFC:门禁卡模拟的踩坑笔记

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华