Python's memory management is efficient and transparent, and developers do not need to worry about managing memory manually.
In python, the Primitive variable objects like integers, floating-point numbers, boolean values are typically stored in the stack. Also Function calls and local variables, function call stack and exception stack are stored in the stack.
Storing these objects on the stack allows for efficient memory management. It is worth noting, however, that not all primitive values are stored on the stack. For example, large arrays or other data structures that exceed a certain size may be stored on the heap instead. Additionally, Python's memory management is dynamic and flexible, so the actual storage location of an object can depend on a variety of factors, including the object's size, lifetime, and usage patterns.
Objects, lists, dictionaries and other data structures are stored on the heap. Now you may get confused thinking the primitive data types also is an object, but it is stored in heap. Then what type of objects stored in Heap?
Objects created from a class are typically stored on the heap, not the stack. This is because class instances are typically long-lived objects that can exist beyond the scope of a single function call.
When you create an instance of a class using the class constructor (i.e. by calling the class with arguments), a new object is created on the heap. The class instance contains all the data members defined in the class and any additional data members that are added dynamically at runtime. The memory required for the object is dynamically allocated and managed by the Python runtime.
For example, consider the following code:
class Person: def __init__(self, name, age): self.name = name self.age = age person1 = Person("John", 30)
In this example, person1 is an object of the Person class that is created on the heap when the Person constructor is called.
Here is an example of a recursive function that could cause a stack overflow:
def recursive_function(x): if x == 0: return 0 else: return x + recursive_function(x-1) result = recursive_function(10000) print(result)
In this example, the recursive_function calls itself recursively with smaller values of x until it reaches the base case where x == 0. However, if x is too large, the function will create a large number of nested function calls on the call stack, eventually causing a stack overflow.
Allocate a large amount of memory using a list
large_list =  * 1000000000
In this example, we create a list with a billion elements by using the multiplication operator to repeat the value 0 a billion times. Since each element in the list takes up memory, this can cause a heap overflow if the system does not have enough memory to allocate a billion elements.
Python also includes a garbage collector that automatically frees up memory that is no longer being used by the program. This helps to prevent memory leaks and makes it easier to write Python programs that don't have to worry about managing memory manually.
Python's garbage collector runs automatically in the background, and most of the time, you don't need to worry about it. However, in some cases, it can be helpful to manually trigger the garbage collector using the gc.collect() function.