Since you can’t generally have a tuple contain itself, some things assume that this can’t happen. So, what breaks? Actually, not all that much. Most things either don’t recurse, or guard properly against infinite recursion (usually because they handle lists and tuples the same way - eg json.dumps() and repr()). I’ve found two things that break, plus one that only breaks in Python 2.7 (IMO that doesn’t really count):
>>> isinstance("spam", tup)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RecursionError: maximum recursion depth exceeded in __instancecheck__
>>> pickle.dumps(tup)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RecursionError: maximum recursion depth exceeded while pickling an object
>>> print(tup)
((...),)
In Python 2, that last one printed out a huge number of open parentheses and then segfaulted. In every Py3 version that I have here (back as far as 3.3), this correctly reports the recursion.
So! What do you know of that special-cases tuples, and can it be busted with this? One point if it raises an unexpected exception, two points if it gets stuck or segfaults.
>>> repr(tup)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File ".../lib/python3.11/collections/__init__.py", line 461, in __repr__
return self.__class__.__name__ + repr_fmt % self
~~~~~~~~~^~~~~~
File ".../lib/python3.11/collections/__init__.py", line 461, in __repr__
return self.__class__.__name__ + repr_fmt % self
~~~~~~~~~^~~~~~
File ".../lib/python3.11/collections/__init__.py", line 461, in __repr__
return self.__class__.__name__ + repr_fmt % self
~~~~~~~~~^~~~~~
[Previous line repeated 496 more times]
RecursionError: maximum recursion depth exceeded while getting the repr of an object
If I get enough tuits, I’ll look into fixing the hash issue, since that one might be worth the effort. The others aren’t.
The main reason for asking was that, over on python-list (it’s not dead yet folks, come back Thursday), we were having a discussion about why tuples and not arbitrary iterables were accepted in various places. I put the explanation that they can’t be self-referential, so recursive descent of tuples can safely be assumed to terminate, and then sought examples where this was the case. Which I guess means that the details are really just for fun.
(Sadly, this Discourse doesn’t have a section for fun, and Python Help is the best we have. Which makes some threads seem a little out of place.)
This implies that given a little ctypes shenanigans, even tuples aren’t really constant, right ? Wouldn’t that break some expectations even aside from recursivity ?
For example some system could expect ints, strings and tuples to be constant and share them between threads or processes without checking anything, and that apparently would be an incorrect assumption.
It’s often said that no container, even frozen dataclasses, is truly constant except tuples, hence most uses of namedtuple, but if even they aren’t…
Well, yes, but unless you have some sort of memory protection, nothing is constant. With ctypes, you can change the value of 17 to be 19. So I’m sure you could do other things like breaking hashes; that’s not really surprising.
However, that aside, it’s still possible to make a recursive tuple using the C API, without violating the normal rules of constancy. That’s because tuples are created with just a length, and then you populate the elements; nothing’s stopping you from using the newly-created tuple as part of itself.