5

I'd like to serialise dataclasses to strings. That's easy enough with dataclasses.asdict and creating a custom __str__ method. It works perfectly, even for classes that have other dataclasses or lists of them as members. However, calling str on a list of dataclasses produces the repr version. I'd like str to produce the serialised form and repr to stay as it is.

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))

The output of the script above is:

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

Curiously, none of the str methods are ever called even when explicitly asking for the string version through the list. If the repr=False is removed, the repr is overwritten and none of the custom methods are called.

The wanted output would be:

>>> 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}]
Felix
  • 2,548
  • 19
  • 48
  • 1
    [The default implementation defined by the built-in type object calls object.__repr__().](https://docs.python.org/3/reference/datamodel.html#object.__str__) – Chillie Sep 12 '19 at 09:35
  • 1
    You never explicitly ask for ``str`` of your data, you ask for ``str`` of the list of your data. Lists implicitly call ``repr`` on their elements. Even if you circumvent this for the top-level or any list, intermediate objects are free to decide how to react to ``str`` as well. – MisterMiyagi Sep 12 '19 at 09:38

1 Answers1

4

The str of a list calls the repr of its elements, that's just how it is implemented. Nothing about how you implement the elements' classes is going to change that. So if you don't want to meddle with the dataclass's __repr__, you would have to use your own collection class instead of list, e.g.:

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']"

If you really want to change the __str__ of a built-in type like list , you could look at forbiddenfruit

user2390182
  • 72,016
  • 6
  • 67
  • 89
  • So it seems. A bit weird, but I'm sure there are reasons for it. Thanks! – Felix Sep 12 '19 at 09:39
  • You can see in the given example, that the `str` of elements loses some information about their types. That might be one reason. Imagine strings containing commas within lists. You wouldn't know anything about such lists from their representation. – user2390182 Sep 12 '19 at 09:43
  • True, but in this context I'd want it to produce a JSON-like output, so information on the type would not be lost. Good point. – Felix Sep 12 '19 at 14:51