Как я должен определить __repr__ для иерархии классов в python?

#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 экземпляра and repr() , будут возвращены их правильные строки (например 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__ .