PAYATHON 2026

如何获取 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 名称一起删掉。

备注:内容仅供参考。