ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python __dict__详解:对象状态的内存快照与工程实践

Python __dict__详解:对象状态的内存快照与工程实践 1. 为什么你写的Python代码总在“看不见的地方”出错从__dict__开始真正看懂对象你有没有遇到过这样的情况明明给一个实例对象设置了属性打印出来却显示为空调试时想看看某个对象到底存了哪些数据dir()返回一堆下划线方法根本找不到自己加的字段用json.dumps(obj)报错说Object of type X is not JSON serializable但翻遍类定义也没发现哪里不合规甚至在写ORM映射、序列化框架或动态配置加载器时反复卡在“这个属性到底存没存进去”的判断上——不是逻辑错是根本不知道Python底层怎么存的。这些问题90%都和一个被严重低估、极少被正确认知的内置属性有关__dict__。它不是魔法也不是黑箱而是Python对象模型最坦诚的一扇窗。__dict__是一个普通字典dict每个具有自定义属性的Python对象绝大多数用户定义的类实例都会自动拥有它里面原样存放着该对象所有可变的、运行时动态添加或修改的实例属性名与值的映射关系。注意关键词“可变的”、“运行时动态”、“实例属性”。它不包含类属性、方法、__slots__限定的属性如果启用了的话也不包含C扩展对象或内置类型如list、str的内部状态。它就是你亲手往对象身上“贴标签”时Python默默帮你记下的那本小账本。我第一次真正理解__dict__是在重构一个配置管理模块时。当时需要把YAML文件解析后的字典一键“注入”到一个配置类的实例里同时还要支持后续任意增删改查。我试过setattr()逐个赋值也试过直接操作obj.__dict__.update(config_dict)后者快了3倍以上且天然支持嵌套字典的扁平化合并。那一刻我才意识到__dict__不是用来“读”的它是Python给你留的、最干净、最高效的对象状态直写通道。它让Python从“声明式语言”瞬间拥有了“命令式内存操作”的能力。这篇文章我就带你从零开始彻底拆解__dict__的全部真相——不是教科书式的定义复述而是像两个老手在茶水间聊经验它在哪、怎么用、为什么这么设计、踩过哪些坑、什么情况下绝对不能碰、以及如何用它写出更健壮、更透明、更容易调试的Python代码。2.__dict__的本质对象状态的“内存快照”而非万能反射工具2.1 它不是dir()的简化版而是dir()的“底层真相”很多初学者会把dir(obj)和obj.__dict__混为一谈认为前者是后者的“美化版”。这是个危险的误解。dir()是一个元信息查询函数它返回的是一个按字母序排列的字符串列表内容包括对象自身的__dict__中所有键即实例属性名所有继承自父类的可访问属性和方法名包括__init__、__str__等特殊方法类中定义的所有类属性名即使未被实例化dir()也会列出通过__getattr__或描述符协议动态提供的属性名如果实现了相应协议而__dict__则纯粹是该对象实例自身在内存中维护的一个字典对象它的键值对就是你用obj.attr value或setattr(obj, attr, value)写入的每一个可变属性。它不关心继承、不关心类定义、不关心协议只忠实地记录“这个对象此刻自己拥有什么”。我们来实测对比一下class Person: species Homo sapiens # 类属性 def __init__(self, name, age): self.name name # 实例属性 self.age age # 实例属性 def greet(self): return fHello, Im {self.name} p Person(Alice, 30) p.city Beijing # 动态添加实例属性 print(dir(p) 返回:) print(dir(p)) print(\nlen(dir(p)) , len(dir(p))) print(\n\np.__dict__ 返回:) print(p.__dict__) print(\nlen(p.__dict__) , len(p.__dict__))输出结果精简关键部分dir(p) 返回: [__class__, __delattr__, __dict__, __dir__, __doc__, __eq__, __format__, __ge__, __getattribute__, __gt__, __hash__, __init__, __init_subclass__, __le__, __lt__, __module__, __ne__, __new__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__, age, city, greet, name, species] len(dir(p)) 41 p.__dict__ 返回: {name: Alice, age: 30, city: Beijing} len(p.__dict__) 3看到区别了吗dir(p)列出了41个名字其中绝大部分是继承来的内置方法和类属性species而p.__dict__只返回了3个键正是你亲手设置的name、age和city。species作为类属性它属于Person类本身存储在Person.__dict__里而不是每个p实例的__dict__中。这就是__dict__最核心的定位它只管“实例自己的东西”不管“家族共有的东西”或“祖传的方法”。提示__dict__是Python实现“动态属性”的基石。当你执行p.height 165时CPython解释器内部做的就是p.__dict__[height] 165。没有__dict__Python就无法支持这种灵活的对象建模方式。2.2__dict__的存储位置每个实例独享一份类本身也有自己的__dict____dict__不是一个全局共享的池子而是每个对象实例都有一份独立的副本。这保证了对象之间的状态隔离。我们可以通过id()函数验证这一点p1 Person(Tom, 25) p2 Person(Jerry, 28) print(p1.__dict__ id:, id(p1.__dict__)) print(p2.__dict__ id:, id(p2.__dict__)) print(p1.__dict__ is p2.__dict__?, p1.__dict__ is p2.__dict__) # 修改p1的属性不影响p2 p1.job Engineer print(p1.__dict__ after add job:, p1.__dict__) print(p2.__dict__ unchanged:, p2.__dict__)输出p1.__dict__ id: 140234567890123 p2.__dict__ id: 140234567890456 p1.__dict__ is p2.__dict__? False p1.__dict__ after add job: {name: Tom, age: 25, job: Engineer} p2.__dict__ unchanged: {name: Jerry, age: 28}每个实例的__dict__内存地址完全不同修改一个另一个完全不受影响。这正是面向对象封装性的物理基础。同时类本身也是一个对象type的实例它也有自己的__dict__。类的__dict__里存的是类属性、方法、__init__等所有定义在类体内的东西。我们来看print(\nPerson.__dict__ keys (first 10):) for i, k in enumerate(Person.__dict__): if i 10: print(f {k}) else: break print(f\n__init__ in Person.__dict__? {__init__ in Person.__dict__}) print(fspecies in Person.__dict__? {species in Person.__dict__}) print(fgreet in Person.__dict__? {greet in Person.__dict__})输出关键部分Person.__dict__ keys (first 10): __module__ __qualname__ __init__ greet __dict__ __weakref__ __doc__ species __init__ in Person.__dict__? True species in Person.__dict__? True greet in Person.__dict__? True看到了吗__init__、greet、species这些都在Person.__dict__里。而p1.__dict__里只有name和age。这就是Python的属性查找链MRO的物理依据当访问p1.species时Python先查p1.__dict__没找到再查Person.__dict__找到了最后才返回Homo sapiens。__dict__就是这条查找链上最底层、最真实的“数据仓库”。2.3__dict__的边界什么对象有什么对象没有为什么并非所有Python对象都有__dict__。它的存在与否取决于对象的类型和定义方式。这是__dict__使用中最容易踩坑的地方。有__dict__的对象所有默认创建的用户自定义类的实例即未启用__slots__的类函数对象func.__dict__可以用来存储函数的元数据模块对象sys.modules[__name__].__dict__就是当前模块的全局命名空间没有__dict__的对象内置类型int,str,list,dict,tuple等的实例。它们的内部状态由C结构体管理不暴露为Python字典。启用了__slots__的类的实例。__slots__是一种内存优化机制它告诉Python“这个类的实例只允许拥有这些固定名称的属性不要给我分配__dict__这个大字典了。”某些C扩展模块创建的对象如numpy.ndarray虽然它有__dict__但其核心数据存储在C数组中__dict__只存元数据我们来验证__slots__的影响class PersonWithSlots: __slots__ [name, age] # 只允许这两个属性 def __init__(self, name, age): self.name name self.age age ps PersonWithSlots(Bob, 35) print(ps.__dict__ exists?, hasattr(ps, __dict__)) try: print(ps.__dict__) except AttributeError as e: print(Error:, e) # 尝试动态添加属性会失败 try: ps.city Shanghai except AttributeError as e: print(Cannot add city:, e)输出ps.__dict__ exists? False Error: PersonWithSlots object has no attribute __dict__ Cannot add city: PersonWithSlots object has no attribute city启用__slots__后ps实例连__dict__属性都没有了自然也无法动态添加新属性。这带来了两个后果一是内存占用大幅降低每个实例节省了约56字节的字典开销二是对象变得“更严格”强制开发者明确声明所有可能的属性提升了代码的可预测性。但这也意味着如果你依赖__dict__来做序列化或调试就必须提前知道这个类是否启用了__slots__。注意__slots__只影响实例的__dict__不影响类本身。PersonWithSlots.__dict__依然存在里面存着__slots__、__init__等。3.__dict__的核心应用场景从调试神器到生产级工具3.1 场景一深度调试与状态快照——告别print()和pprint()在复杂系统中尤其是涉及多层嵌套对象、动态属性、第三方库集成时print(obj)往往只显示一个模糊的__main__.Person object at 0x...pprint.pprint(obj)也无济于事。此时obj.__dict__就是你的“X光机”。实操技巧递归打印对象树import pprint def deep_dict_snapshot(obj, max_depth3, current_depth0): 生成对象的深度字典快照处理嵌套对象 if current_depth max_depth: return f... nested too deep (depth{current_depth}) # 如果是基本类型直接返回 if isinstance(obj, (str, int, float, bool, type(None))): return obj # 如果是字典递归处理其值 if isinstance(obj, dict): return {k: deep_dict_snapshot(v, max_depth, current_depth1) for k, v in obj.items()} # 如果是列表/元组递归处理其元素 if isinstance(obj, (list, tuple)): return [deep_dict_snapshot(v, max_depth, current_depth1) for v in obj] # 如果是对象尝试获取其__dict__并递归处理 if hasattr(obj, __dict__): result {} for key, value in obj.__dict__.items(): # 过滤掉明显是方法或内置属性的键 if not key.startswith(__) or key in [__dict__, __module__]: result[key] deep_dict_snapshot(value, max_depth, current_depth1) return result # 其他类型尝试str() return str(obj) # 使用示例 class Address: def __init__(self, street, city): self.street street self.city city class Employee: def __init__(self, name, salary, address): self.name name self.salary salary self.address address # 嵌套对象 self.tags [active, senior] # 列表 addr Address(123 Main St, Beijing) emp Employee(Charlie, 15000, addr) emp.department {name: Engineering, head: Alice} # 字典 print( Deep Snapshot of emp ) pprint.pprint(deep_dict_snapshot(emp), width80, depth4)这个函数会输出一个清晰、可读的嵌套字典精确展示emp实例及其所有嵌套对象address、department的__dict__内容。它比任何IDE的变量监视器都更透明因为它是纯Python代码你可以随时修改、过滤、导出为JSON。我在排查一个ORM缓存失效问题时就是靠这个函数一眼就发现了缓存对象里多了一个_cache_timestamp属性而数据库查询结果里没有从而定位到是缓存装饰器的bug。3.2 场景二高效对象克隆与浅拷贝——比copy.copy()快3倍copy.copy()和copy.deepcopy()是标准库方案但它们需要遍历所有属性、处理循环引用、调用各种钩子函数开销不小。对于结构简单、没有循环引用、且你完全掌控其__dict__内容的对象直接操作__dict__是最快的克隆方式。实操步骤手动实现超轻量级克隆def fast_shallow_clone(obj): 基于__dict__的极速浅拷贝适用于无__slots__、无不可序列化属性的类 # 创建一个新实例不调用__init__避免副作用 clone obj.__class__.__new__(obj.__class__) # 直接复制__dict__这是最核心的一步 clone.__dict__.update(obj.__dict__) return clone # 性能对比测试 import timeit p Person(David, 40) p.hobby [reading, swimming] p.profile {level: senior, years: 10} # 方法1copy.copy time_copy timeit.timeit(lambda: copy.copy(p), number1000000) # 方法2fast_shallow_clone time_fast timeit.timeit(lambda: fast_shallow_clone(p), number1000000) print(fcopy.copy time: {time_copy:.4f}s) print(ffast_shallow_clone time: {time_fast:.4f}s) print(fSpeedup: {time_copy/time_fast:.1f}x)在我的测试环境中fast_shallow_clone比copy.copy快了3.2倍。原理很简单copy.copy要做安全检查、类型判断、递归调用而fast_shallow_clone只做两件事——__new__分配内存update复制字典。它假设你了解自己的对象知道它没有__slots__没有需要特殊处理的属性如文件句柄、锁对象。这是一种典型的“用确定性换性能”的工程权衡。注意此方法是浅拷贝。如果p.__dict__中的某个值是可变对象如列表、字典、另一个实例那么克隆体和原对象会共享这个值。如果需要深拷贝可以在update之后对clone.__dict__中的可变值进行copy.deepcopy但这会损失一部分性能优势。3.3 场景三动态配置加载与对象初始化——一行代码完成批量赋值在Web开发、数据处理脚本中经常需要将一个字典如JSON解析结果、YAML配置、数据库查询行快速映射到一个对象的属性上。setattr()循环固然可行但__dict__.update()更简洁、更高效。实操要点安全的字典更新与过滤def load_config_to_object(obj, config_dict, allow_unknownTrue, strict_modeFalse): 将配置字典安全地加载到对象中 :param obj: 目标对象 :param config_dict: 配置字典 :param allow_unknown: 是否允许config_dict中存在obj.__dict__没有的键 :param strict_mode: 严格模式禁止覆盖已存在的属性用于只读配置 if not hasattr(obj, __dict__): raise TypeError(fObject {type(obj).__name__} has no __dict__) # 获取对象当前已有的属性名集合 existing_keys set(obj.__dict__.keys()) # 确定要更新的键 if strict_mode: # 严格模式只更新已存在的属性 keys_to_update existing_keys set(config_dict.keys()) else: # 默认模式更新所有键但可选择是否允许新增 keys_to_update set(config_dict.keys()) if not allow_unknown: keys_to_update keys_to_update existing_keys # 执行更新 for key in keys_to_update: if strict_mode and key in existing_keys: # 严格模式下跳过已存在的属性保持其原始值 continue obj.__dict__[key] config_dict[key] return obj # 使用示例 class AppConfig: def __init__(self): self.host localhost self.port 8000 self.debug False config_data { host: prod-server.com, port: 443, debug: True, timeout: 30, # 这个键在AppConfig.__init__中没有定义 ssl_enabled: True } app_cfg AppConfig() print(Before load:, app_cfg.__dict__) # 允许未知键默认 load_config_to_object(app_cfg, config_data, allow_unknownTrue) print(After load (allow unknown):, app_cfg.__dict__) # 输出: {host: prod-server.com, port: 443, debug: True, timeout: 30, ssl_enabled: True} # 不允许未知键只更新已存在的 app_cfg2 AppConfig() load_config_to_object(app_cfg2, config_data, allow_unknownFalse) print(After load (no unknown):, app_cfg2.__dict__) # 输出: {host: prod-server.com, port: 443, debug: True}这个函数的关键在于allow_unknown和strict_mode参数。在生产环境配置加载中“允许未知键”意味着配置可以向前兼容新版本配置多了字段旧代码忽略即可“不允许未知键”则意味着强契约确保配置文件不会意外引入无效字段。而__dict__.update()在这里扮演了“原子化写入”的角色比循环setattr()少了N次属性查找和方法调用的开销。3.4 场景四序列化与反序列化——绕过json模块的限制json.dumps()要求对象必须是dict、list、str、int、float、bool或None。当你想序列化一个自定义对象时通常得写一个复杂的default函数。而__dict__提供了一条捷径只要你的对象没有__slots__且所有属性都是JSON可序列化的那么json.dumps(obj.__dict__)就是最简单的方案。实操进阶处理嵌套对象与日期import json from datetime import datetime class Order: def __init__(self, order_id, items, created_atNone): self.order_id order_id self.items items # list of dicts self.created_at created_at or datetime.now() class Item: def __init__(self, name, price): self.name name self.price price # 创建一个复杂对象 item1 Item(Laptop, 1200.0) item2 Item(Mouse, 25.5) order Order(ORD-001, [item1, item2]) # 直接序列化会失败 try: json.dumps(order) except TypeError as e: print(Direct json.dumps fails:, e) # 方案1朴素__dict__序列化失败因为datetime不可序列化 try: json.dumps(order.__dict__) except TypeError as e: print(Plain __dict__ fails:, e) # 方案2自定义JSONEncoder处理datetime和嵌套对象 class OrderEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, datetime): return obj.isoformat() if hasattr(obj, __dict__): # 递归处理嵌套对象 return obj.__dict__ return super().default(obj) # 序列化 json_str json.dumps(order, clsOrderEncoder, indent2) print(Serialized JSON:) print(json_str) # 反序列化从JSON字符串重建对象 def dict_to_order(data): 将字典数据反序列化为Order对象 order Order(data[order_id], data[items]) order.created_at datetime.fromisoformat(data[created_at]) return order # 解析回对象 parsed_data json.loads(json_str) restored_order dict_to_order(parsed_data) print(f\nRestored: {restored_order.order_id}, {restored_order.created_at})这里的关键洞察是__dict__让你能把一个复杂的对象图瞬间“压平”成一个标准的Python字典然后交给json模块处理。你只需要专注于编写JSONEncoder来处理那些“非标准”的类型如datetime、Decimal、自定义枚举而不用为每个类都写一个to_dict()方法。这在微服务API开发中非常实用可以快速为DTOData Transfer Object类建立统一的序列化入口。4.__dict__的陷阱与避坑指南那些年我们踩过的坑4.1 陷阱一__dict__不是“万能属性容器”它会被__slots__和property绕过这是新手最容易犯的错误以为obj.__dict__里能看到对象所有的“东西”。实际上__dict__只包含通过赋值语句直接写入的、可变的实例属性。以下几种情况属性不会出现在__dict__中property装饰的属性它们是方法访问时触发getter逻辑其值不存储在__dict__里。__slots__限定的属性如前所述它们被存储在C层面的固定偏移量中不经过__dict__。描述符Descriptor如classmethod、staticmethod、自定义的描述符类其行为由__get__、__set__方法控制状态可能存储在描述符实例自身而非宿主对象的__dict__中。我们来演示class BankAccount: __slots__ [_balance] # 使用__slots__ def __init__(self, initial_balance0): self._balance initial_balance property def balance(self): return self._balance balance.setter def balance(self, value): if value 0: raise ValueError(Balance cannot be negative) self._balance value def __str__(self): return fBankAccount(balance{self.balance}) acc BankAccount(1000) print(acc.__dict__:, acc.__dict__) # AttributeError! print(acc._balance (via __slots__):, acc._balance) print(acc.balance (via property):, acc.balance) # 尝试动态添加属性会失败 try: acc.owner John Doe except AttributeError as e: print(Cannot add owner:, e)输出acc.__dict__: AttributeError: BankAccount object has no attribute __dict__ acc._balance (via __slots__): 1000 acc.balance (via property): 1000 Cannot add owner: BankAccount object has no attribute owner这里balance是property你看到的是计算结果但acc.__dict__根本不存在。_balance是__slots__的一部分它被存储在对象的固定内存布局中而不是字典里。所以永远不要假设__dict__能代表一个对象的全部状态。在调试时如果__dict__是空的第一反应应该是检查这个类是否用了__slots__或者属性是否是property。4.2 陷阱二直接修改__dict__可能导致对象不一致__dict__是一个普通的dict你可以用obj.__dict__[key] value直接赋值。这很高效但也极其危险因为它绕过了类的__setattr__方法和所有属性验证逻辑。实操案例绕过验证导致的数据污染class ValidatedPerson: def __init__(self, name, age): self.name name # 触发__setattr__ self.age age # 触发__setattr__ def __setattr__(self, name, value): if name age: if not isinstance(value, int) or value 0 or value 150: raise ValueError(fAge must be an integer between 0 and 150, got {value}) super().__setattr__(name, value) def __str__(self): return fValidatedPerson(name{self.name}, age{self.age}) vp ValidatedPerson(Eve, 28) print(Initial:, vp) # 正常方式设置会触发验证 try: vp.age 200 except ValueError as e: print(Normal assignment caught error:, e) # 危险方式直接修改__dict__绕过验证 vp.__dict__[age] 200 print(After __dict__ bypass:, vp) # 输出: ValidatedPerson(nameEve, age200) print(vp.age is now invalid but no error raised!)输出Initial: ValidatedPerson(nameEve, age28) Normal assignment caught error: Age must be an integer between 0 and 150, got 200 After __dict__ bypass: ValidatedPerson(nameEve, age200) vp.age is now invalid but no error raised!这个例子展示了__dict__的“双刃剑”本质。它给了你底层的自由但也要求你承担全部的责任。在生产代码中除非你有非常充分的理由比如性能瓶颈、框架内部实现否则绝不应该直接写obj.__dict__[key] value。你应该始终走setattr(obj, key, value)或obj.key value这条路让类的约束逻辑生效。__dict__的正确用法是“读取”和“批量更新”如配置加载而不是“单点篡改”。4.3 陷阱三__dict__的浅拷贝特性——修改嵌套对象会“传染”前面提到__dict__.update()是浅拷贝这在处理嵌套的可变对象列表、字典、其他实例时会带来意想不到的副作用。实操演示共享引用的“幽灵bug”class Team: def __init__(self, name, members): self.name name self.members members # 这是一个列表 t1 Team(Alpha, [Alice, Bob]) t2 fast_shallow_clone(t1) # 使用前面定义的fast_shallow_clone print(t1.members id:, id(t1.members)) print(t2.members id:, id(t2.members)) print(t1.members is t2.members?, t1.members is t2.members) # 修改t2的members列表 t2.members.append(Charlie) print(t1.members after t2 change:, t1.members) # [Alice, Bob, Charlie] print(t2.members after t2 change:, t2.members) # [Alice, Bob, Charlie]输出t1.members id: 140234567890123 t2.members id: 140234567890123 t1.members is t2.members? True t1.members after t2 change: [Alice, Bob, Charlie] t2.members after t2 change: [Alice, Bob, Charlie]t1和t2的members列表是同一个对象因为__dict__.update()只是复制了指向这个列表的指针而不是复制了列表本身。这在多线程环境下尤其危险一个线程修改t2.members另一个线程读t1.members就会得到脏数据。解决方案深度克隆嵌套结构import copy def safe_deep_clone(obj): 安全的深度克隆处理嵌套的可变对象 clone obj.__class__.__new__(obj.__class__) # 对__dict__中的每个值进行深度拷贝 for key, value in obj.__dict__.items(): clone.__dict__[key] copy.deepcopy(value) return clone t3 safe_deep_clone(t1) print(t3.members id:, id(t3.members)) print(t1.members is t3.members?, t1.members is t3.members) t3.members.append(David) print(t1.members after t3 change:, t1.members) # [Alice, Bob, Charlie] print(t3.members after t3 change:, t3.members) # [Alice, Bob, Charlie, David]输出t3.members id: 140234567890456 t1.members is t3.members? False t1.members after t3 change: [Alice, Bob, Charlie] t3.members after t3 change: [Alice, Bob, Charlie, David]这个safe_deep_clone函数在__dict__的基础上对每个值都做了copy.deepcopy确保了完全的隔离。它比copy.deepcopy(obj)更快因为你跳过了deepcopy的通用类型判断和钩子调用直接针对__dict__操作。这是在需要高性能深度克隆时的黄金方案。4.4 陷阱四__dict__与继承——子类__dict__不包含父类的私有属性Python的“私有”属性以双下划线__开头会被名称改写name mangling这会影响__dict__的可见性。class Parent: def __init__(self): self.public_attr Im public self.__private_attr Im private # 名称被改写为 _Parent__private_attr class Child(Parent): def __init__(self): super().__init__() self.child_attr Im child c Child() print(Child.__dict__ keys:, list(c.__dict__.keys())) print(c.__dict__[public_attr]:, c.__dict__.get(public_attr)) print(c.__dict__[_Parent__private_attr]:, c.__dict__.get(_Parent__private_attr)) print(c.__dict__[__private_attr]:, c.__dict__.get(__private_attr)) # None输出Child.__dict__ keys: [public_attr, _Parent__private_attr, child_attr] c.__dict__[public_attr]: Im public c.__dict__[_Parent__private_attr]: Im private c.__dict__[__private_attr]: NoneParent的__private_attr在Child实例的__dict__中键名是_Parent__private_attr而不是__private_attr。这是Python为了防止子类意外覆盖父类私有属性而做的保护。因此在遍历__dict__时如果你期望看到__private_attr是找不到的。你需要知道名称改写的规则或者干脆避免在需要被__dict__访问的场景中使用双下划线私有化。5.__dict__的高级技巧与实战延伸超越基础用法5.1 技巧一用__dict__实现简易的“数据类”Dataclass LitePython 3.7 有dataclass但在一些老项目或需要极致控制的场景你可以用__dict__自己造一个轻量级
返回列表