Python - nested __repr__ reverts newline to "\\n"
Asked Answered
P

1

5
class MyClass:
    def __init__(self):
        self.list_ = []
    def __repr__(self):
        return '\n'.join(['this','should','all','be','on','separate','lines']) + str([str(list_val) for list_val in self.list_])

myClass = MyClass()
myClass.list_.append(MyClass())
myClass.list_[0].list_.append(MyClass())
print(myClass)

I would expect this code to print:

this
should
all
be
on
separate
lines[this
should
all
be
on
separate
lines[this
should
all
be
on
separate
lines]]

or something similar, but instead it prints

this
should
all
be
on
separate
lines["this\nshould\nall\nbe\non\nseparate\nlines['this\\nshould\\nall\\nbe\\non\\nseparate\\nlines[]']"]

That is, when I try to convert an object to a string while already inside the __repr__ method of another object of the same class, it turns a newline into \n, and if I nest it further it results in \\n, and each time I nest it it adds an additional backslash before the escape sequence.

After reading this question, it seems that the __repr__ method thinks I actually want the two characters \ and n, but I don't: I want the escape sequence \n. Is there any way to override this and force it to interpret it as a newline rather than two separate characters?

Prudie answered 31/12, 2015 at 4:18 Comment(0)
S
8

The problem is that the repr of a string converts special characters to escape sequences. This means if you call repr recursively on a string with special characters, backslashes will pile up:

>>> print("First line\nSecond line")
First line
Second line
>>> print(repr("First line\nSecond line"))
'First line\nSecond line'
>>> print(repr(repr("First line\nSecond line")))
"'First line\\nSecond line'"

You are encountering this because your __repr__ calls str on a list, and the str of a list uses repr (not str) on each item in the list:

>>> print('First line\nSecond line')
First line
Second line
>>> print(['First line\nSecond line'])
['First line\nSecond line']

Note that the \n appears here, just like it did for calling repr on the string itself in the first example. That's because lists call repr on their contents to display themselves.

So by doing str([...]), you are calling repr on the contents of your list, which means you're calling repr recursively your nested objects, which means backslashes pile up as you saw.

If you want to avoid this, you need to avoid calling repr on the nested objects. You can do this by manually crafting a string using join, similar to what you already did, instead of calling str on a list:

def __repr__(self):
    innerRepr = '['+'\n'.join(str(list_val) for list_val in self.list_) + ']' if self.list_ else ''
    return '\n'.join(['this','should','all','be','on','separate','lines']) + innerRepr

Then your print(myClass) gives your desired result.

Siltstone answered 31/12, 2015 at 5:5 Comment(1)
Wow, amazing answer -- difficulty level 11.Johannajohannah

© 2022 - 2024 — McMap. All rights reserved.