如何获取 Python 对象的所有实例属性,包括继承的 __slots__?
结论
对于 CPython 中采用常规属性存储方式的 Python 自定义类,可以合并实例 __dict__ 的键和继承链上已赋值的 slot 名称。
处理 slots 时,检查类创建后生成的描述符即可,无需重新遍历 __slots__。这种方法可以处理继承的 slots,也不受一次性迭代器已经耗尽的影响。
如果要覆盖任意 Python 对象,就没有通用函数能保证完整列出所有“实例属性”。通过 __getattr__ 动态生成的属性、扩展类型内部的数据和普通实例实际存储的字段,需要分别定义枚举规则。
为什么检查 slot 描述符
定义 __slots__ 时,Python 会为相应名称在类上创建描述符。即使声明 slots 的迭代器已经耗尽,这些描述符仍然存在。遍历 type(o).__mro__ 可以找到继承的 slots。Python 数据模型文档
在 CPython 中,可以通过 types.MemberDescriptorType 识别常规 slot 描述符。描述符位于类的 __dict__ 中,对应的数据却存储在各个实例中,因此不能排除类字典中的所有名称。types.MemberDescriptorType 文档
获取实际存储的属性名称
以下实现面向普通 Python 自定义类,假设 __dict__ 和 slot 描述符没有被替换或刻意遮蔽:
from types import MemberDescriptorType
def instance_attribute_names(obj):
names = set()
# 读取实例字典,绕过自定义的 __getattribute__。
try:
instance_dict = object.__getattribute__(obj, "__dict__")
except AttributeError:
pass
else:
names.update(instance_dict)
# 检查当前类及其所有基类上的 slot 描述符。
for cls in type(obj).__mro__:
for name, descriptor in vars(cls).items():
if not isinstance(descriptor, MemberDescriptorType):
continue
if name in {"__dict__", "__weakref__"}:
continue
try:
descriptor.__get__(obj, type(obj))
except AttributeError:
# slot 尚未赋值,或值已被删除。
continue
names.add(name)
return sorted(names)
这里直接调用 slot 描述符的 __get__(),判断存储位置是否有值。如果使用 getattr(obj, name),空 slot 引发的 AttributeError 可能触发自定义 __getattr__() 并返回动态值,导致空 slot 被误判为已赋值。
函数根据是否抛出 AttributeError 判断 slot 状态,因此也会保留值为 None 的 slot 名称。集合用于合并重复名称,最后排序,方便查看结果。
同时包含继承、空 slot 和实例字典的示例
class Base:
__slots__ = ("base_value", "unused", "__dict__")
class_only = "shared"
class Child(Base):
__slots__ = (f"slot_{n}" for n in range(3))
obj = Child()
obj.base_value = 10
obj.slot_1 = None
obj.extra = 20
print(instance_attribute_names(obj))
# ['base_value', 'extra', 'slot_1']
根据示例中的赋值,结果包含基类 slot base_value、子类 slot slot_1 和实例字典中的 extra。未赋值的 unused、slot_0、slot_2 以及类属性 class_only 都不会进入结果。
原问题中的生成器表达式需要加上括号,合法写法是:
class Foo:
__slots__ = (f"slot_{n}" for n in range(3))
适用范围与注意事项
- 该函数枚举实例存储中的名称,不包含普通方法、
property或动态生成的属性,也主动排除了与存储机制相关的__dict__和__weakref__。 - 双下划线开头的私有 slot 会以名称改写后的形式出现,例如
_Example__value。 - 返回名称不保证通过
getattr()能读取到同一个存储值。自定义属性访问逻辑或同名描述符可能改变读取行为。 - 如果 slot 描述符被覆盖或删除,该函数无法保证恢复对应的存储信息;对于 C 扩展类型和其他 Python 实现,也需要按其具体机制处理。
不要使用 set(dir(obj)) - set(dir(type(obj))) 过滤类成员:slot 描述符本来就在类上,这种差集会把需要保留的 slot 名称一起删掉。
备注:内容仅供参考。
