Сериализация списка классов данных: str всегда вызывает repr

Я хотел бы сериализовать классы данных в строки. Это достаточно просто с dataclasses.asdict и созданием собственного метода __str__. Он отлично работает даже для классов, членами которых являются другие классы данных или их списки. Однако вызов str для списка классов данных дает версию repr. Я бы хотел, чтобы str создавал сериализованную форму, а repr оставался как есть.

import json
from dataclasses import dataclass, asdict


@dataclass
class Ser:
    def __repr__(self):
        print('repr called')
        return json.dumps(asdict(self))

    def __str__(self):
        print('str called')
        return json.dumps(asdict(self))


@dataclass(repr=False)
class C(Ser):
    i: int

    def __str__(self):
        print('child str called')
        return super().__str__()


list_in = json.loads('[{"i": 1}, {"i": 2}]')
data = [C(**i) for i in list_in]
print(data)
print(repr(data))
print(str(data))

Вывод скрипта выше:

repr called
repr called
[{"i": 1}, {"i": 2}]
repr called
repr called
[{"i": 1}, {"i": 2}]
repr called
repr called
[{"i": 1}, {"i": 2}]

Любопытно, что ни один из методов str никогда не вызывается даже при явном запросе версии строки через список. Если repr=False удаляется, repr перезаписывается, и ни один из пользовательских методов не вызывается.

Желаемый результат будет:

>>> data  # I guess inspecting with the command line always calls repr
[C(i=1), C(i=2)]
>>> repr(data)
[C(i=1), C(i=2)]
>>> str(data)
[{"i": 1}, {"i": 2}]

person Felix    schedule 12.09.2019    source источник
comment
Вы никогда явно не запрашиваете str ваших данных, вы запрашиваете str из списка ваших данных. Списки неявно вызывают repr для своих элементов. Даже если вы обойдете это для верхнего уровня или любого другого списка, промежуточные объекты также могут решать, как реагировать на str.   -  person MisterMiyagi    schedule 12.09.2019


Ответы (1)


str из list вызывает repr его элементов, именно так это реализовано. Ничто в том, как вы реализуете классы элементов, не изменит этого. Поэтому, если вы не хотите вмешиваться в __repr__ класса данных, вам придется использовать свой собственный класс коллекции вместо list, например:

class StrList(list):
    def __str__(self):
        return '[' + ', '.join(str(x) for x in self) + ']'


lst = StrList([1, '1'])
str(lst)
# '[1, 1]'
repr(lst)
# "[1, '1']"

Если вы действительно хотите изменить __str__ встроенного типа, такого как list , вы можете посмотреть forbiddenfruit

person schwobaseggl    schedule 12.09.2019
comment
Ну, это похоже. Немного странно, но я уверен, что на это есть причины. Спасибо! - person Felix; 12.09.2019
comment
Вы можете видеть в данном примере, что str элементов теряет некоторую информацию об их типах. Это может быть одной из причин. Представьте строки, содержащие запятые в списках. Вы ничего не узнаете о таких списках из их представления. - person schwobaseggl; 12.09.2019
comment
Верно, но в этом контексте я бы хотел, чтобы вывод производился в формате JSON, чтобы информация о типе не терялась. Хорошая точка зрения. - person Felix; 12.09.2019