#python #python-3.x #slots #repr
#python #python-3.x #слоты #repr
Вопрос:
Учитывая следующий код:
class Base:
__slots__ = 'base_field',
def __init__(self, base_field):
self.base_field = base_field
def __repr__(self):
return f"{self.__class__.__name__}(base_field={self.base_field!r})"
class Derived(Base):
__slots__ = 'derived_field',
def __init__(self, derived_field, **kwargs):
super().__init__(**kwargs)
self.derived_field = derived_field
def __repr__(self):
return f"{self.__class__.__name__}(base_field={self.base_field!r}, derived_field={self.derived_field!r})"
b1 = Base('base')
print(repr(b1))
b2 = eval(repr(b1))
d1 = Derived(base_field='dbase', derived_field='derived')
print(repr(d1))
d2 = eval(repr(d1))
Этот код сломается, если я добавлю новое поле Base
и забуду обновить Derived
класс ‘ __repr__
.
Как и где я должен определить __repr__
метод (ы), чтобы:
- Когда я передаю оба
Base
Derived
экземпляра andrepr()
, будут возвращены их правильные строки (напримерrepr(Base("base")) == "Base(base_field="base")
, иrepr(Derived(base_field='dbase', derived_field='derived')) == "Derived(base_field="base", derived_field="derived")
- Я могу безопасно добавлять поля в
Base
класс, и эти поля будут отображаться вDerived
__repr__
выходных данных класса. Другими словами, функцияDerived
класса__repr__
не должна знать о полях вBase
классе. - Код минимален. Если мне нужно добавить больше пары строк, дополнительный код может того не стоить
Я читаю некоторый код, в котором автор решил проблему, выполнив вызов Derived
класса __repr__
super().__repr__()
, а затем удалил последнюю скобку и добавил Derived
атрибуты к строке. Это выполняет свою работу, но мне интересно, есть ли более питонический способ достижения этой цели. Или, возможно, это Pythonic способ?
Я изучил Python dataclass
, и это отлично помогает создавать __repr__
s, которые делают правильные вещи. Однако они используют __dict__
s, поэтому необходимо учитывать компромиссы (например, меньший объем памяти для объекта, который имеет много экземпляров одновременно, или проще __repr__
).
Спасибо!
Комментарии:
1. Я нахожусь на мобильном телефоне, поэтому я не могу проверить, но вы можете взглянуть на
self.__dict__
2. Если вы пытаетесь сериализовать и десериализовать свои экземпляры, не используйте комбинацию
repr
иeval
. Напишите явные (де) функции сериализации.3. @chepner Спасибо за совет. Мы не используем этот код для целей сериализации.
4. @Asocia спасибо за руководство. К сожалению, мы используем слоты , поэтому классы не имеют dict .
Ответ №1:
Вы можете сделать это динамически, просто проанализируйте слоты и обязательно выполните MRO:
class Base:
__slots__ = 'base_field',
def __init__(self, base_field):
self.base_field = base_field
def __repr__(self):
slots = [
slot
for klass in type(self).mro() if hasattr(klass, '__slots__')
for slot in klass.__slots__
]
args = ", ".join(
[f"{slot}={getattr(self, slot)!r}" for slot in reversed(slots)]
)
return f"{type(self).__name__}({args})"
class Derived(Base):
__slots__ = 'derived_field',
def __init__(self, derived_field, **kwargs):
super().__init__(**kwargs)
self.derived_field = derived_field
Вы можете просто поместить это в функцию и повторно использовать код в различных иерархиях.
def slotted_repr(obj):
slots = [
slot
for klass in type(obj).mro() if hasattr(klass, '__slots__')
for slot in klass.__slots__
]
args = ", ".join(
[f"{slot}={getattr(obj, slot)!r}" for slot in reversed(slots)]
)
return f"{type(obj).__name__}({args})"
Комментарии:
1. Я не могу решить, является ли это более злым или гениальным, но мне это нравится.
2. @juanpa.arrivillaga Я исследовал
mro
подход, и он сделал все, что я хотел, но я надеялся, что есть более питонический способ, о котором я не знал. Мне действительно нравится идея разложить его на функции. Объедините это с функцией с именем dict_repr, и это было бы очень полезно. Спасибо!!3. Для потомков
hasattr()
проверка важна, потому что object, последний класс в MRO, не имеет__slots__
.