并非所有Python对象都有__dict__属性,如使用__slots__、内置类型、C扩展类等会触发AttributeError;应优先用vars()并配合过滤或专用序列化方法。

直接访问 __dict__ 为什么有时会报错
不是所有 Python 对象都有 __dict__ 属性。比如使用 __slots__ 定义的类、内置类型(如 int、list)、或者用 C 实现的扩展类(如 datetime.datetime),调用 obj.__dict__ 会触发 AttributeError: 'X' object has no attribute '__dict__'。
常见错误场景:
- 定义了
__slots__ = ['name', 'age']的类,实例没有__dict__ - 继承自
namedtuple或dataclass(frozen=True)的类 - 对第三方库中封装过的对象(如某些 ORM 模型)直接硬取
__dict__
此时应优先改用 vars() —— 它内部做了安全检查,遇到无 __dict__ 的对象会直接抛错,但至少不掩盖问题本质。
vars() 和 __dict__ 的行为差异
vars(obj) 本质上就是 obj.__dict__ 的封装,但它要求参数必须是“有 __dict__ 的对象”,否则报 TypeError: vars() argument must have __dict__ attribute。而手动写 obj.__dict__ 是更底层的访问,失败时抛的是 AttributeError。
立即学习“Python免费学习笔记(深入)”;
实操建议:
- 调试阶段用
vars(obj)更稳妥,错误信息更明确 - 生产代码中若已确认类没用
__slots__,两者可互换;但别假设所有实例都支持 - 注意:
vars()不接受空参(vars()本身等价于locals()),只接受单个对象
示例:
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
<p>p = Person("Alice", 30)
print(vars(p)) # {'name': 'Alice', 'age': 30}
print(p.<strong>dict</strong>) # 同上,结果一致
处理嵌套对象和非字典友好属性
直接用 vars() 或 __dict__ 只做浅拷贝,不会递归转字典。如果实例属性里有另一个自定义类对象、函数、lambda、模块、文件句柄等,它们不会自动变成可序列化的格式。
典型问题:
-
self.profile = UserProfile(...)→ 字典里存的是对象地址,不是内容 -
self.callback = lambda x: x * 2→ 序列化时报TypeError: cannot serialize '_io.TextIOWrapper' object -
self._private = "secret"→ 会被包含进去(__dict__不过滤私有属性)
解决思路:
- 加一层过滤:用字典推导式剔除方法、下划线开头的属性(如
{k: v for k, v in vars(obj).items() if not k.startswith('_') and not callable(v)}) - 对嵌套对象显式调用其自身的
to_dict()方法(推荐约定接口) - 避免依赖
__dict__做 JSON 序列化,改用dataclasses.asdict()或pydantic.BaseModel.dict()
dataclass 和 Pydantic 场景下别硬用 __dict__
对于 @dataclass 类,默认生成的 __dict__ 是可用的,但要注意:
- 如果用了
field(default_factory=list),__dict__里是实际值,没问题 - 但如果用了
init=False或repr=False的字段,__dict__仍包含它(因为存储仍在),而asdict()会按声明逻辑处理
Pydantic v2 的 BaseModel 实例默认禁用 __dict__(走的是 __pydantic_core_schema__),直接访问会得到空字典或报错。正确方式是:
- v1:
obj.dict() - v2:
obj.model_dump()(推荐)或obj.model_dump(mode='json')
强行用 vars(obj) 在这些场景下大概率返回空或意外结构,容易埋坑。
真正需要通用转换逻辑时,别图省事硬套 __dict__ —— 它只是实现细节,不是契约。类的设计者是否暴露内部状态,得看它自己提供的接口。

















