Recursion is a process in which a function calls itself repeatedly until a certain condition is met.  It  is implemented by declaring a function that calls itself within its body.  For example:

#The following recursive function prints all the numbers from n down up to 0

def countdown(n):
    print(n)
    if n == 0:
       return 0

    #the function calls itself
    countdown(n-1)

countdown(10)

A brief overview of recursion

Recursion is the process of defining a problem in terms of itself. It involves breaking down a problem into smaller and simpler sub-problems and then combining their solutions until a base case is reached. 

The base case must always be included in the recursive function definition for the recursion to eventually terminate.  For example in our previous example, the base case is reached when the value of 'n' becomes equal to zero.

When a recursive function does not define a base case or the base case is improperly defined, the function will not terminate and  will instead enter into what is called an infinite recursion. 

When is RecursionError exception raised?

Recursion is a highly resource intensive process. Python in an aim to save memory memory from a recursive call sets a recursion limit. This limit prevents the program from running out of memory, as even a simple recursive function call  can quickly use up all of the available memory on a computer. The recursion limit set by Python is by default,  1000, which means that a function can only call itself up to 1000 times before causing an error. Whenever this limit is reached, a RecursionError is raised.

For example the following function calls itself but does not define a base case leading to the recursion limit being reached and consequently raising the RecursionError.

def func():
    #The function calls itself without setting a base case
    func()

func()
//Traceback (most recent call last):
//  File "<stdin>", line 1, in <module>
//  File "<stdin>", line 3, in func
//  File "<stdin>", line 3, in func
//  File "<stdin>", line 3, in func
//  [Previous line repeated 996 more times]
//RecursionError: maximum recursion depth exceeded

Consider the following example:

def double_countdown(n):
   
    print(n)
    if n == 0:
        return n
    double_countdown(n - 2)

#with an even number
double_countdown(10)
//10
//8
//6
//4
//2
//0

#with an odd number
double_countdown(9)
//9
//7
//3
//1
//-1
//-3
//...
//Traceback (most recent call last):
//  File "<stdin>", line 1, in <module>
//  ...
//  File "<stdin>", line 3, in double_countdown
//RecursionError: maximum recursion depth exceeded while calling a Python object

In the above case, the recursive function works properly with even numbers, this is if you continue subtracting two to an even number, you will eventually reach 0( the base case) thus terminating the recursion.  On the case with an odd number, you will never get zero by continuously subtracting two, thus it eventually leads to the recursion limit being exceeded and consequently,  RecursionError is raised.  This is a case where the base case is improperly set to handle all the likely inputs.

Avoiding and Handling the RecursionError

The most obvious way to avoid a recursion error is to ensure that the base case is correctly set. 

The builtin sys module also offers way to directly increase or decrease the recursion limit. By setting the recursion limit to a value bigger then the default(1000), functions can be able to make more recursive calls.

import sys

#get the current recursion limit
print(sys.getrecursionlimit())

#Change the recursion limit
sys.setrecursionlimit(2000)

print(sys.getrecursionlimit())

You should, however, be careful to not set the recursion limit too high, the value is set to 1000 for a good reason. Setting the recursion limit too high can lead to a stack overflow issues which can easily cause your program to crash. As a rule of thumb always try to make sure the recursion limit is below 2000.

We can also use the try-except blocks to catch the RecursionError exception as it is raised. 

def double_countdown(n):
   
    print(n)
    if n == 0:
        return n
    double_countdown(n - 2)

try:
   double_countdown(9)
except RecursionError as e:
   print("Recursion limit was reached. ")
//9
//7
//5
//3
//1
//-1
//-2
//....
//Recursion limit was reached.