What is the maximum recursion depth in Python, and how to increase it?
It is a guard against a stack overflow, yes. Python (or rather, the CPython implementation) doesn't optimize tail recursion, and unbridled recursion causes stack overflows. You can check the recursion limit with sys.getrecursionlimit
:
import sys
print(sys.getrecursionlimit())
and change the recursion limit with sys.setrecursionlimit
:
sys.setrecursionlimit(1500)
but doing so is dangerous -- the standard limit is a little conservative, but Python stackframes can be quite big.
Python isn't a functional language and tail recursion is not a particularly efficient technique. Rewriting the algorithm iteratively, if possible, is generally a better idea.
How could you increase the maximum recursion depth in Python?
import sys
sys.setrecursionlimit(2000)
Set maximum recursion depth in java
Create this class:
public class RecursionLimiter {
public static int maxLevel = 10;
public static void emerge() {
if (maxLevel == 0)
return;
try {
throw new IllegalStateException("Too deep, emerging");
} catch (IllegalStateException e) {
if (e.getStackTrace().length > maxLevel + 1)
throw e;
}
}
}
Then import static and insert emerge()
call into the beginning of any method in your code that can be deeply recursive. You can adjust maximum allowed recursion level via the maxLevel
variable. The emerge()
procedure will interrupt execution on a level greater than the value of that variable. You can switch off this behaviour by setting maxLevel
to 0
. This solution is thread-safe because it doesn't use any counter at all.
RecursionError: maximum recursion depth exceeded in comparison
When a RecursionError
is raised, the python interpreter may also offer you the context of the call that caused the error. This only serves for debugging, to give you a hint where in your code you should look in order to fix the problem.
See for example this circular str
-call setup that leads to a different message:
>>> class A:
... def __str__(self):
... return str(self.parent)
>>> a = A()
>>> a.parent = a
>>> str(a)
RecursionError: maximum recursion depth exceeded while calling a Python object
There is no documentation of this behaviour on the issue discussion where RecursionError
was introduced, but you can just search the cpython code for occurences of Py_EnterRecursiveCall. Then you can see the actual contexts that will be returned depending on where the error is raised:
Py_EnterRecursiveCall(" while encoding a JSON object")
Py_EnterRecursiveCall(" while pickling an object")
Py_EnterRecursiveCall(" in __instancecheck__")
Py_EnterRecursiveCall(" in __subclasscheck__")
Py_EnterRecursiveCall(" in comparison")
Py_EnterRecursiveCall(" while getting the repr of an object")
Py_EnterRecursiveCall(" while getting the str of an object")
Py_EnterRecursiveCall(" while calling a Python object")
Py_EnterRecursiveCall("while processing _as_parameter_") # sic
# .. and some more that I might have missed
How do you set the maximum recursion depth in?
Ah. Found it by reading the error message. This will set the recursion depth to 100000
> options(expressions= 100000)
What is the recursion depth and why is set to a fixed limit?
A function is recursive if it calls itself either directly or via some other function that it calls. While recursion is a useful way to build an algorithm it can be a resource hog. Each time the function is called, it creates some local state (the local variables) and memory space remains in use until the function finally returns. If your recursive calls go a million deep, that's a million local frames in memory.
The risk is that recursion will blow up the stack frame or process memory and you will get a hard failure of the code. This is a really bad error as the entire program exits with no ability to clean up. Even if the program doesn't crash, it uses a lot of memory, impacting the rest of your system. There could be more swapping and worse case, the Linux oom killer could randomly kill processes in an attempt to get resources for the kernel.
To mitigate risk from either accidental recursion or a buggy algorithm that doesn't properly limit itself, python puts a limit on recursion. But developers can override that limit via sys.setrecursionlimit(some_huge_number)
in which case anything bad is on their heads.
Maximum recursion depth exceeded when finding the depth of binary-search-tree
The recursion depth should not be that great on the condition that your binary tree is relatively balanced. Still, even when your binary tree is degenerate, like is what happens when you create it from a sorted sequence (sorted
) then you should still only need O(n) stack space.
But your code for both bst_build
and find_depth
needs a lot more stack space. This is because they don't backtrack when reaching a leaf. Instead they both restart a traversal from the root, without getting out of the current recursion tree first:
bst_build
does this by callingbst_build
again when it wants to insert the next input fromseq
. This should happen in a loop (outside of recursion), not when you are already deep in a recursion tree.find_depth
does this by callingfind_depth
again when it reaches a leaf. This should happen by first backtracking one step and then recurring again into a sibling node. Instead it restarts the search from the root (which is a waste of time), and does so without getting out of recursion first.
This makes this code need a tremendous amount of stack space, which will be like O(n²) in the worst (degenerate) case.
Not related to your question, but it is a pity -- and completely unnecessary -- that find_depth
destroys the tree as it is being traversed.
Another problem that is not related to your question: your bst_check
is not correct. It will return True for the following tree:
2
/
1
\
3
Yet, this tree is not a valid BST. The requirement for a valid BST is not only that a left child is less than its parent, but that all nodes in the left subtree are less than that parent.
Here is a fix for all these issues:
def bst_build(seq):
if not seq:
return None
def add_child(main, value):
if value == main[0]:
return
childindex = 1 if main[0] > value else 2
if not main[childindex]:
main[childindex] = [value, None, None]
else:
add_child(main[childindex], value)
binary_search_tree = [seq[0], None, None]
for value in seq[1:]:
add_child(binary_search_tree, value)
return binary_search_tree
def bst_depth(b):
return 1 + max(bst_depth(b[1]) if b[1] else -1, bst_depth(b[2]) if b[2] else -1)
def bst_check(b, low=float("-inf"), high=float("inf")):
return not b or (low <= b[0] <= high and bst_check(b[1], low, b[0])
and bst_check(b[2], b[0], high))
Related Topics
How to Add Row to Stargazer Table to Indicate Use of Fixed Effects
Terminating an Apply-Based Function Early (Similar to Break)
Using The Result of Summarise (Dplyr) to Mutate The Original Dataframe
How to Extract Variable Names from a Netcdf File in R
Is There More Efficient or Concise Way to Use Tidyr::Gather to Make My Data Look 'Tidy'
How to Use Custom Cross Validation Folds with Xgboost
How to Predict Survival Probabilities in R
How to Read All Files in One Directory into R at Once
How to Install Doredis Package Version 1.0.5 into R 3.0.1 on Windows
Add Geom_Line to Link All The Geom_Point in Boxplot Conditioned on a Factor with Ggplot2
Getting Stargazer Column Labels to Print on Two or Three Lines
Generating Split-Color Rectangles from Ggplot2 Geom_Raster()
How to Split a Dataframe Column by The First Instance of a Character in Its Values
Get Plot() Bounding Box Values
How Does R Represent Na Internally
Ggplot2 Equivalent of 'Factorization or Categorization' in Googlevis in R