Список атрибутов объекта: функции dir, vars и другие инструменты Python
Список атрибутов объекта в Python
Основной способ: функция vars() и атрибут __dict__
Как получить список пользовательских атрибутов экземпляра класса?
Встроенная функция vars() возвращает словарь __dict__ объекта. Этот словарь содержит только те атрибуты, которые были явно присвоены экземпляру через self или напрямую. Для классов vars() возвращает атрибуты самого класса (включая методы).
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
p = Person('Alice', 30)
print(vars(p))
# {'name': 'Alice', 'age': 30}
атрибуты класса python (атрибуты классов и объектов в python)
{'name': 'Alice', 'age': 30}
библиотека классов python (библиотека классов в python)
Атрибут __dict__ даёт тот же результат:
print(p.__dict__)
# {'name': 'Alice', 'age': 30}
метод объекта python (методы объектов в python)
Проблемы и ошибки
- Не все объекты имеют
__dict__. Например, экземпляры классов со слотом__slots__не хранят атрибуты в__dict__. В этом случаеvars(obj)вызовет TypeError. - Словарь
__dict__не включает унаследованные атрибуты или атрибуты, определённые на уровне класса (если они не переопределены в экземпляре). - Для встроенных типов (например,
int,str)__dict__отсутствует.
Когда использовать:
- Когда нужны только атрибуты, назначенные непосредственно объекту.
- В сериализации (например, преобразование в JSON) или логировании состояния.
Вариант 1: функция dir() - полный список всех имён
Как получить все имена, доступные через объект, включая унаследованные и специальные методы?
Функция dir() возвращает отсортированный список строк - имён атрибутов и методов объекта. Она полезна для исследования объекта, но содержит много служебных элементов (начинающихся с двойного подчёркивания).
print(dir(p)[:10])
# ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__']
Python структура объекта (структура объекта в python)
Проблемы и ошибки
- Список может быть очень большим, особенно для сложных встроенных классов.
- Не различает атрибуты и методы, не даёт информации о значениях.
Когда использовать:
- Для быстрой интроспекции объекта в интерактивной среде.
- При отладке, чтобы увидеть весь доступный интерфейс.
Вариант 2: модуль inspect - гибкий инструмент
Как получить атрибуты с фильтрацией по типу (например, только методы или только свойства)?
Функция inspect.getmembers() возвращает список кортежей (имя, значение) для всех членов объекта, которые удовлетворяют предикату. Это мощный способ для детального анализа.
import inspect
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius ** 2
c = Circle(5)
methods = inspect.getmembers(c, predicate=inspect.ismethod)
print(methods)
# [('__init__', ), ('area', )]
Python создание объектов (создание объектов в python)
[('__init__', ), ('area', )]
Self object python (объект self в python)
Проблемы и ошибки
- Вызов
inspect.getmembers()может быть медленным для больших объектов. - Предикаты могут случайно вызывать геттеры (если атрибут является свойством).
Когда использовать:
- Когда нужно отфильтровать только методы, только атрибуты определённого класса или наследуемые члены.
- При написании декораторов или метапрограммировании.
Вариант 3: обход __dict__ с проверкой типа
Как получить только пользовательские атрибуты, исключая методы класса?
Можно пройти по __dict__ объекта и отсеять служебные имена. Для экземпляра __dict__ содержит только данные.
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
def info(self):
return f'{self.title} by {self.author}'
b = Book('1984', 'Orwell')
attrs = {k: v for k, v in b.__dict__.items() if not k.startswith('_')}
print(attrs)
# {'title': '1984', 'author': 'Orwell'}
Object attribute python (атрибуты объекта в python)
{'title': '1984', 'author': 'Orwell'}
Python call method (вызов метода в python)
Проблемы и ошибки
- Не учитывает свойства (property), которые могут быть определены в классе и не отображаются в
__dict__экземпляра. - Если класс использует
__slots__,__dict__отсутствует.
Когда использовать:
- Для сериализации, когда нужно только состояние экземпляра.
- В ORM или дата-классах для перебора полей.
Вариант 4: использование __annotations__ (аннотации типов)
Как получить атрибуты, объявленные с аннотацией типа?
Атрибут класса __annotations__ содержит словарь имён атрибутов с их типами. Это полезно для классов, где аннотации обязательны (например, дата-классы).
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
print(Point.__annotations__)
# {'x': float, 'y': float}
Python класс данных (класс данных в python)
{'x': <class 'float'>, 'y': <class 'float'>}
Проблемы и ошибки
- Доступен только на уровне класса, не для экземпляров.
- Не включает динамически добавленные атрибуты.
Когда использовать:
- В метапрограммировании, для генерации кода или валидации на основе аннотаций.
Расширенные примеры получения атрибутов объекта
Рекурсивный обход атрибутов с исключением служебных
Иногда требуется собрать все атрибуты объекта, включая вложенные. Следующая функция рекурсивно обходит атрибуты, игнорируя служебные (начинающиеся с подчеркивания) и основные встроенные типы.
def deep_attrs(obj, max_depth=2):
"""Рекурсивно собирает атрибуты объекта."""
result = {}
if max_depth <= 0:
return result
for name in dir(obj):
if name.startswith('_'):
continue
try:
value = getattr(obj, name)
except (AttributeError, TypeError):
continue
if hasattr(value, '__dict__') or hasattr(value, '__slots__'):
result[name] = deep_attrs(value, max_depth - 1)
else:
result[name] = repr(value)
return result
class Config:
host = 'localhost'
port = 8080
class App:
def __init__(self):
self.config = Config()
self.name = 'MyApp'
a = App()
import pprint
pprint.pprint(deep_attrs(a))
{'config': {'host': "'localhost'", 'port': '8080'},
'name': "'MyApp'"}
Получение только свойств (properties) с помощью inspect
Свойства (дескрипторы) не отображаются в __dict__ экземпляра, но их можно найти через класс.
import inspect
class Temperature:
def __init__(self, celsius):
self._celsius = celsius
@property
def fahrenheit(self):
return self._celsius * 9/5 + 32
@property
def kelvin(self):
return self._celsius + 273.15
obj = Temperature(100)
# Получаем свойства из класса
properties = inspect.getmembers(type(obj), predicate=lambda x: isinstance(x, property))
print(properties)
# [('fahrenheit', ), ('kelvin', )]
[('fahrenheit', ), ('kelvin', )]
Фильтрация атрибутов по типу значения (строки, числа, кастомные объекты)
from typing import get_type_hints
class User:
def __init__(self, username, age):
self.username = username
self.age = age
self._id = 1
user = User('ivan', 25)
# Получаем только атрибуты со значениями строкового типа
str_attrs = {k: v for k, v in vars(user).items() if isinstance(v, str)}
print(str_attrs)
# {'username': 'ivan'}
{'username': 'ivan'}
Использование __slots__ и получение атрибутов для таких классов
Классы с __slots__ не имеют __dict__, поэтому vars() не работает. Список имён атрибутов можно получить через сам __slots__.
class Point2D:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
pt = Point2D(1, 2)
# vars(pt) выдаст TypeError
# Используем __slots__
slot_names = pt.__class__.__slots__
print(slot_names)
# ('x', 'y')
values = {slot: getattr(pt, slot) for slot in slot_names}
print(values)
# {'x': 1, 'y': 2}
('x', 'y')
{'x': 1, 'y': 2}
Динамическое добавление атрибутов и их перечисление
class DynamicObj:
pass
obj = DynamicObj()
obj.foo = 100
obj.bar = 'hello'
# После добавления атрибуты появятся в __dict__
print(vars(obj))
# {'foo': 100, 'bar': 'hello'}
# Удаление атрибута
if hasattr(obj, 'foo'):
delattr(obj, 'foo')
print(vars(obj))
# {'bar': 'hello'}
{'foo': 100, 'bar': 'hello'}
{'bar': 'hello'}
Сравнение производительности dir(), vars() и __dict__
import timeit
class Test:
def __init__(self):
self.a = 1
self.b = 2
self.c = 3
t = Test()
print(timeit.timeit('dir(t)', globals={'t': t}, number=100000))
print(timeit.timeit('vars(t)', globals={'t': t}, number=100000))
print(timeit.timeit('t.__dict__', globals={'t': t}, number=100000))
# Примерный вывод (зависит от системы):
# 0.32
# 0.07
# 0.04
0.321456 0.070123 0.041987
Как видно, прямой доступ к __dict__ самый быстрый, vars() чуть медленнее, а dir() существенно медленнее из-за сбора унаследованных имён.