Unambiguous repr for recursive objects

Currently repr() for recursive object replaces its recurred representation wish a placeholder containing "...". For list this is "[...]":
a = [1, 2]
a.append(a) a
[1, 2, [...]] For dict this is "{...}":
d = {1: 2}
d[3] = d
d
{1: 2, 3: {...}} For OrderedDict (Python implementation or 3.4-) this is just "...":
The problem is that "[...]", and "{...}", and just "..." are valid Python expressions and above representations can be evaluated to different objects. I propose to use uniform and unambiguous non-evaluable representation for recursive objects. I have two ideas: 1. "<...>". Plus: this is as short as "[...]" and "{...}". Minus: we loss even a little tip about the type of recurred object. 2. Use the default implementation, object.__repr__(). E.g. "<list object at 0xb7111498>". Plus: we get even more information than before. Not just exact name of the type, but the identifier of the object. This can be useful in the case of complex structure containing a number of potentially recursive objects. Minus: it is longer.

On 26 December 2015 at 19:28, Serhiy Storchaka <storchaka@gmail.com> wrote:
The problem is that "[...]", and "{...}", and just "..." are valid Python expressions and above representations can be evaluated to different objects.
I believe this is just an oversight from when "..." became usable outside subscripts in 3.0, but I agree it's a discrepancy worth addressing.
I think that would still be an improvement - the hinting only works for types with native syntax anyway, while for arbitrary containers it's already necessary to fall back to a generic notation like "<...>".
2. Use the default implementation, object.__repr__(). E.g. "<list object at 0xb7111498>".
Another minor disadvantage is that it's not as easy to write doctests or simple examples, as the repr isn't predictable (or you have to put a "..." in as a placeholder for the ID anyway). A larger disadvantage is that you can't readily spot that it's a recursive reference, since that's implicit in the ID of the given object. To make this less abstract, here's a simple example: >>> a = [1, 2] >>> b = [a] >>> a.append(b) >>> a [1, 2, [[...]]] >>> b [[1, 2, [...]]] With the first alternative, that becomes: >>> a [1, 2, [<...>]] >>> b [[1, 2, <...>]] I think that's actually clearer than the status quo (since the circular reference is more visually distinct), but it would retain the current ambiguity if the containers also reference themselves: >>> a.append(a) >>> b.append(b) >>> a [1, 2, [[...], [...]], [...]] >>> b [[1, 2, [...], [...]], [...]] That scenario is likely rare enough not to worry about - visualising such data structures sensibly is tough in general, and arguably best left to use case specific display routines, rather than trying to handle it with the default container repr. If the recursive display changed to use object.__repr__ instead, we'd get something like: >>> object.__repr__(a) '<list object at 0x7fc755edd488>' >>> object.__repr__(b) '<list object at 0x7fc755eea0c8>' >>> a [1, 2, [<list object at 0x7fc755edd488>, <list object at 0x7fc755eea0c8>], <list object at 0x7fc755edd488>] >>> b [[1, 2, <list object at 0x7fc755eea0c8>, <list object at 0x7fc755edd488>], <list object at 0x7fc755eea0c8>] So +1 from me for switching to "<...>" in 3.6+ to make the default recursive repr for containers an invalid expression again, but only +0 for using the full object.__repr__ - the extra precision in the more complex case hurts readability in the typical case, without really improving readability in the complex cases that would be the intended beneficiaries. Regards, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Nick Coghlan <ncoghlan@gmail.com> writes:
That clarifies it quite well. The fact ‘<...>’ is not valid syntax is an improvement: it helps to signal this is a display for something that can't be simply serialised. The ‘<’, ‘>’ enclosing characters also have a nice symmetry with the default representation of so many types. +1 to change the representation of “recursive references” to ‘<...>’. -- \ “The surest way to corrupt a youth is to instruct him to hold | `\ in higher esteem those who think alike than those who think | _o__) differently.” —Friedrich Nietzsche, _The Dawn_, 1881 | Ben Finney

On 26 December 2015 at 19:28, Serhiy Storchaka <storchaka@gmail.com> wrote:
The problem is that "[...]", and "{...}", and just "..." are valid Python expressions and above representations can be evaluated to different objects.
I believe this is just an oversight from when "..." became usable outside subscripts in 3.0, but I agree it's a discrepancy worth addressing.
I think that would still be an improvement - the hinting only works for types with native syntax anyway, while for arbitrary containers it's already necessary to fall back to a generic notation like "<...>".
2. Use the default implementation, object.__repr__(). E.g. "<list object at 0xb7111498>".
Another minor disadvantage is that it's not as easy to write doctests or simple examples, as the repr isn't predictable (or you have to put a "..." in as a placeholder for the ID anyway). A larger disadvantage is that you can't readily spot that it's a recursive reference, since that's implicit in the ID of the given object. To make this less abstract, here's a simple example: >>> a = [1, 2] >>> b = [a] >>> a.append(b) >>> a [1, 2, [[...]]] >>> b [[1, 2, [...]]] With the first alternative, that becomes: >>> a [1, 2, [<...>]] >>> b [[1, 2, <...>]] I think that's actually clearer than the status quo (since the circular reference is more visually distinct), but it would retain the current ambiguity if the containers also reference themselves: >>> a.append(a) >>> b.append(b) >>> a [1, 2, [[...], [...]], [...]] >>> b [[1, 2, [...], [...]], [...]] That scenario is likely rare enough not to worry about - visualising such data structures sensibly is tough in general, and arguably best left to use case specific display routines, rather than trying to handle it with the default container repr. If the recursive display changed to use object.__repr__ instead, we'd get something like: >>> object.__repr__(a) '<list object at 0x7fc755edd488>' >>> object.__repr__(b) '<list object at 0x7fc755eea0c8>' >>> a [1, 2, [<list object at 0x7fc755edd488>, <list object at 0x7fc755eea0c8>], <list object at 0x7fc755edd488>] >>> b [[1, 2, <list object at 0x7fc755eea0c8>, <list object at 0x7fc755edd488>], <list object at 0x7fc755eea0c8>] So +1 from me for switching to "<...>" in 3.6+ to make the default recursive repr for containers an invalid expression again, but only +0 for using the full object.__repr__ - the extra precision in the more complex case hurts readability in the typical case, without really improving readability in the complex cases that would be the intended beneficiaries. Regards, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Nick Coghlan <ncoghlan@gmail.com> writes:
That clarifies it quite well. The fact ‘<...>’ is not valid syntax is an improvement: it helps to signal this is a display for something that can't be simply serialised. The ‘<’, ‘>’ enclosing characters also have a nice symmetry with the default representation of so many types. +1 to change the representation of “recursive references” to ‘<...>’. -- \ “The surest way to corrupt a youth is to instruct him to hold | `\ in higher esteem those who think alike than those who think | _o__) differently.” —Friedrich Nietzsche, _The Dawn_, 1881 | Ben Finney
participants (3)
-
Ben Finney
-
Nick Coghlan
-
Serhiy Storchaka