Для большинства объектов это просто: просто проверьте o.__dict__ либо напрямую, либо через vars(). Однако это осложняется существованием __slots__:
- Если класс определяет __slots__, у него даже нет есть __dict__, и в этом случае vars() и o.__dict__ даже не будут работать
- Если, конечно, это не включает __dict__ in __slots__, и в этом случае они будут иметься, но они будут перечислять только те атрибуты, которые не имеют свои собственные слоты
- Слот может быть пустым, поэтому то, что атрибут указан там, не означает, что этот экземпляр действительно имеет этот атрибут
- Класс может наследовать slots, включая слот __dict__, из его родительских классов, поэтому я не могу просто перебирать type(o).__slots__
- Технически __slots__ не обязательно должен быть списком или кортежем; это может быть любой итерируемый объект, включая неповторяемый итератор, который будет использоваться при выполнении определения класса. Например. class Foo: __slots__ = f"slot_{n}" for n in range(3) совершенно легален и создаст класс с 3 слотами ("slot_0", "slot_1" и "slot_2"), но Foo.__slots__ просто вернет исчерпанный итератор, а не список слотов.
- Она не различает полные и пустые слоты. Это не фатальный недостаток, поскольку я мог бы просто попытаться получить каждый атрибут с помощью getattr() и перехватить AttributeError, чтобы найти пустые, но это раздражает.
- Он возвращает класс, а также члены экземпляра, которые мне очень не нужны, и я не знаю какого-либо простого и надежного способа их отфильтровать.