Это то, что я использую:
def todict(obj):
""" Return the object's dict excluding private attributes,
sqlalchemy state and relationship attributes.
"""
excl = ('_sa_adapter', '_sa_instance_state')
return {k: v for k, v in vars(obj).items() if not k.startswith('_') and
not any(hasattr(v, a) for a in excl)}
class Base:
def __repr__(self):
params = ', '.join(f'{k}={v}' for k, v in todict(self).items())
return f"{self.__class__.__name__}({params})"
Base = declarative_base(cls=Base)
Для любых моделей, которые наследуются от Base
, будет определен метод по умолчанию __repr__()
, и если мне нужно сделать что-то другое, я могу просто переопределить метод наэтот конкретный класс.
Он исключает значение любых частных атрибутов, обозначенных начальным подчеркиванием, объекта состояния экземпляра SQLAlchemy и любых атрибутов отношений из строки.Я исключаю атрибуты отношений, так как чаще всего не хочу, чтобы repr вызывал ленивую загрузку отношений, а когда отношения двунаправленные, в том числе атрибуты отношений могут вызывать бесконечную рекурсию.
Результат выглядит следующим образом: ClassName(attr=val, ...)
.
- EDIT -
Функция todict()
, о которой я упоминал выше, является помощником, к которому я часто обращаюсь для создания dict
из объекта SQLAВ основном для сериализации.Я лениво использовал его в этом контексте, но он не очень эффективен, поскольку он создает dict
(в todict()
) для построения dict
(в __repr__()
).С тех пор я изменил шаблон для вызова генератора:
def keyvalgen(obj):
""" Generate attr name/val pairs, filtering out SQLA attrs."""
excl = ('_sa_adapter', '_sa_instance_state')
for k, v in vars(obj).items():
if not k.startswith('_') and not any(hasattr(v, a) for a in excl):
yield k, v
Тогда базовая база выглядит следующим образом:
class Base:
def __repr__(self):
params = ', '.join(f'{k}={v}' for k, v in keyvalgen(self))
return f"{self.__class__.__name__}({params})"
Функцию todict()
можно использовать вне keyvalgen()
Генератор также, но больше не нужен для создания repr.