TIL: Python Recursion Limit When Accessing Elements in Recursive List
Earlier today I found this post by Ryan Westlund. It reminded me that in Python you add a reference to a list to itself, like this:
>>> my_list = [1]
>>> my_list.append(my_list)
>>> my_list
[1, [...]]
>>> my_list[1]
[1,[...]]
>>> my_list[1][1][1]
[1, [...]]
Sidenote: this is one of the cases where reference counting breaks down and why Python needs a garbage collector (GC). It is the GCs job is to clean up circular references like these. Now, back to the story!
Here we have recursion and in Python, we have a maximum recursion limit. If you have ever forgotten the base case in a recursive function you probably have seen this error:
RecursionError: maximum recursion depth exceeded during compilation
which is triggered when the limit is reached.
How does accessing the elements work? You can give your own classes the []-operator by implementing __getitem__(self, key)
. So we can probably think about doing this my_list[1][1]
as recursive calls to __getitem__
:
list.__getitem__(
list.__getitem__(my_list, 1),
1
)
It, therefore, seems reasonable that this would trigger the max recursion error if repeated enough times. What is the default maximum recursion limits?
>>> import sys
>>> sys.getrecursionlimit()
1000
Wow, that is more than the times I am willing to write [1] to test my hypothesis. Luckily for us, or at least for me, we can change the recursion limit to let's say 5 using sys.setrecursionlimit
.
>>> sys.setrecursionlimit(5)
>>> my_list[1][1][1][1][1]
[1, [...]]
It must be an off by one error, let's do 6 access instead.
>>> my_list[1][1][1][1][1][1]
[1, [...]]
What!?!?
>>> my_list[1][1][1][1][1][1][1][1][1][1][1][1][1][1]
RecursionError: maximum recursion depth exceeded during compilation
Success!!! So, I found 14 access is what is needed for the maximum recursion error to trigger. Interestingly, I only needed 8 when using exec:
>>> exec("my_list" + "[1]"*8)
RecursionError: maximum recursion depth exceeded during compilation
I'm actually still not sure what causes this behavior. It is demonstrably true that accessing the elements can cause a RecursionError, however, I do not understand why it took 14 access with a recursion limit of 5. Might it be some sort of optimization going on here? Even more interesting is that using exec takes 8 accesses, more than the limit but less than the other method. If you have any idea on what might be causing the behavior, please leave a comment below. It would be really interesting to hear your thoughts on this one!