Sitemap

How Does Python Memory Management Work? — CWN

12 min readJan 15, 2024

--

Press enter or click to view image in full size
Python Memory Management Work

Introduction: Simplifying Python Memory Management

Hey there! Have you ever been in a situation where you’re trying to keep your desk organized while juggling multiple tasks? You know, keeping the important papers handy, tossing out the unnecessary clutter, all while making sure you have enough space to work efficiently. Well, that’s a lot like how Python handles memory management — it’s all about organizing and optimizing space, but in the case of Python, it’s in your computer’s memory.

Python, as you might already know, is like the cool kid on the programming block. It’s super popular for its easy-to-read syntax and powerful capabilities. One of its coolest features? Automatic memory management. Unlike some other languages where you have to manually allocate and deallocate memory (which can be pretty tedious and error-prone), Python does most of this heavy lifting for you. It’s like having an intelligent assistant who constantly tidies your workspace without you needing to lift a finger.

So why is this so great? Imagine having to manually keep track of every single piece of data in your program — where it is, how much space it takes, when it’s no longer needed… exhausting, right? With Python, you get to focus more on solving problems and building cool stuff, rather than on the nitty-gritty of memory management.

In this article, we’re going to unravel the mysteries of how Python handles memory. We’ll dive into its smart strategies like reference counting and garbage collection (don’t worry, it’s not as complicated as it sounds). By the end of this, you’ll not only appreciate Python’s memory management system but also pick up some handy tips to optimize your Python code. So, let’s get started and unlock the secrets of Python’s memory management! ??

Understanding Reference Counting in Python

Alright, let’s talk about reference counting in Python. Imagine you’re at a party and you’ve got this plate of delicious cookies (who doesn’t love cookies, right?). Every time someone takes a cookie, you make a note. And when all the cookies are gone, you know it’s time to wash the plate. In Python, reference counting works in a similar way, but instead of cookies, we’re keeping track of references to objects.

What Are We Going to Do in This Example?

We’re going to create a simple Python object and then play around with references to it. We’ll add references, remove some, and observe how Python keeps track of these changes. This is like checking how many people are still eyeing the last few cookies on your plate!

Coding Example: Reference Counting in Action

import sys

# Step 1: Creating a simple object
my_favorite_number = 42

# Step 2: Let's see how many references it has initially
initial_refs = sys.getrefcount(my_favorite_number)
print("Initial references:", initial_refs)

# Step 3: Adding more references
another_number = my_favorite_number
one_more_number = my_favorite_number

# Step 4: Now, let's see the reference count
refs_after_adding = sys.getrefcount(my_favorite_number)
print("References after adding more:", refs_after_adding)

# Step 5: Removing a reference
del another_number

# Step 6: Final reference count
final_refs = sys.getrefcount(my_favorite_number)
print("Final references:", final_refs)

Explanation:

  • Step 1: We create a simple object, my_favorite_number, and assign it the value 42.
  • Step 2: We use sys.getrefcount() to see how many references there are to our number. This is like counting how many people know about our secret cookie stash.
  • Step 3 & 4: We create two more references to the same number. It’s like telling two more friends about the cookies.
  • Step 5 & 6: We remove one reference (del another_number). This is like one friend deciding they don't want cookies anymore. Finally, we check how many references are left.

Expected Output (Visualized in Table Format):

Action                                Reference Count
Initial creation 3
After adding more references 5
After removing a reference 4

Real-World Example:

Think of a popular song. Initially, only a few people know about it. As it gets shared (like our number 42), its “reference count” increases. When people stop listening to it, the count decreases. If it reaches a point where no one listens to it anymore (

reference count hits zero), it’s like the song being removed from people’s playlists — it’s no longer taking up space.

Quiz:

  1. What happens to the reference count of an object in Python when you create a new reference to it? A) Increases by 1 B) Decreases by 1 C) Stays the same D) Doubles
  2. If we delete all references to a Python object, what happens? A) It stays in memory forever. B) It’s marked for garbage collection. C) It becomes a global variable. D) It’s automatically printed to the console.

Answer Key:

  1. A) Increases by 1 — Just like when more people learn about a popular song, the reference count goes up each time a new reference is made.
  2. B) It’s marked for garbage collection — Similar to how a song is removed from playlists when no one listens to it, the object is marked for cleanup from memory.

Garbage Collection in Python: Keeping Our Code Clean

Imagine you’re at a beach party. Everyone’s having fun, eating, drinking, and of course, generating a lot of trash. Now, what if no one picks up this trash? The beach would become a mess, right? In the world of Python programming, ‘garbage collection’ is the unsung hero that cleans up this ‘trash’. It makes sure that the memory your program uses doesn’t turn into a cluttered beach.

What’s Our Plan with the Example?

We’re going to create some objects in Python and intentionally create a situation where they are no longer needed (think of them as empty soda cans at our beach party). Then, we’ll watch Python’s garbage collector in action as it cleans up these objects, keeping our memory beach pristine.

Coding Example: Demonstrating Garbage Collection

import gc

# Step 1: Create a class to represent our objects
class BeachToy:
def __init__(self):
print("Beach Toy Created")
def __del__(self):
print("Beach Toy Cleaned Up")

# Step 2: Enable explicit garbage collection
gc.enable()

# Step 3: Create a function that creates objects
def create_toys():
toy1 = BeachToy()
toy2 = BeachToy()

# Step 4: Call the function
create_toys()

# Step 5: Manually trigger garbage collection
collected_items = gc.collect()
print(f"Items collected by garbage collector: {collected_items}")

Explanation:

  • Step 1: We define a BeachToy class. Creating an instance of this class is like bringing a new toy to the beach. When Python cleans it up, it's like the toy being taken away from the beach.
  • Step 2: We turn on garbage collection with gc.enable(). This is like hiring a cleanup crew for after the party.
  • Step 3 & 4: We create a function create_toys that creates beach toy objects. When the function is called, these toys are created (and used at the party).
  • Step 5: After the function call, the toys are no longer needed (the party’s over). We then manually trigger the garbage collector and see how many items it cleaned up.

Expected Output (Visualized as Messages):

Beach Toy Created
Beach Toy Created
Beach Toy Cleaned Up
Beach Toy Cleaned Up
Items collected by garbage collector: 2

Real-World Example:

Think of it like cleaning up after a big event. If you don’t, things get messy and it’s harder to have more fun events (or run more programs) later. Python’s garbage collector helps us by cleaning up unused objects, ensuring our program’s memory space is like a clean, inviting beach ready for more activities.

Interactive Element:

How about a quick challenge to test your understanding?

Challenge: Modify the code above to

create a circular reference between two BeachToy objects and see how the garbage collector handles it. Circular references are like two friends at the party holding hands and refusing to leave unless the other leaves too. Can the garbage collector still clean them up?

Steps for the Challenge:

  1. Modify the BeachToy class to include a reference to another BeachToy.
  2. In the create_toys function, create two BeachToy instances and make them reference each other (forming a circular reference).
  3. Trigger the garbage collector and observe the outcome.

Expected Learning Outcome: This exercise will help you understand how Python’s garbage collector can identify and clean up even complex situations like circular references. It’s like having a smart cleanup crew who knows how to gently convince those two friends to leave the beach together, ensuring no one is left behind.

Let’s Dive Deeper:

Garbage collection in Python is a sophisticated process. It’s not just about cleaning up; it’s about smartly identifying what needs to be cleaned up and when. Understanding this can help you write more efficient and memory-friendly Python code. It’s like being a responsible party host who knows how to keep the beach clean while ensuring everyone has a good time.

Get Muhammad Nazam’s stories in your inbox

Join Medium for free to get updates from this writer.

By mastering garbage collection, you’re taking a big step towards being a Python pro, capable of writing clean, efficient, and memory-conscious code. Remember, a clean beach (or memory space) means more room for fun (or more code)!

Preventing Memory Leaks in Python: Keep Your Code Shipshape

Picture yourself as the captain of a ship. This ship, much like a Python program, needs to be well-maintained to run smoothly. If you’re not careful, leaks can spring up (just like in a real ship), and these ‘memory leaks’ can cause your program to slow down or crash. So, as a good captain, how do you prevent these leaks? Let’s dive into the world of Python and learn how to keep our ship seaworthy.

What’s Our Mission in This Example?

We’re going to create a scenario in Python where a memory leak might happen, and then I’ll show you how to fix it. Think of it like spotting a small leak in our ship and patching it up before it becomes a problem.

Coding Example: Spotting and Fixing a Memory Leak

# Let's import a tool to help us keep track
import tracemalloc

# Step 1: Start tracing memory allocations
tracemalloc.start()

# Step 2: Create a function that could cause a memory leak
def leaky_function():
# Imagine this list as a cargo container on our ship
cargo_list = []
for _ in range(1000):
# Accidentally creating a leak by adding too much cargo
cargo_list.append("Excess Cargo")

# Step 3: Call the function
leaky_function()

# Step 4: Check the current memory status
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')

# Let's see the top statistics
for stat in top_stats[:5]:
print(stat)

# Step 5: Fixing the leak
def fixed_function():
# Properly managing the cargo
cargo_list = []
for _ in range(1000):
# Adding just the right amount of cargo
cargo_list.append("Necessary Cargo")
return cargo_list

# Call the fixed function
fixed_function()

# Step 6: Check memory status again
new_snapshot = tracemalloc.take_snapshot()
new_top_stats = new_snapshot.statistics('lineno')

# Comparing the results
for stat in new_top_stats[:5]:
print(stat)

Explanation:

  • Step 1 & 2: We’re using tracemalloc to monitor our memory usage, much like using a tool to detect leaks on our ship.
  • Step 3: Our leaky_function is like overloading the ship with cargo, which could lead to a leak.
  • Step 4: We take a snapshot of memory usage to identify potential leaks.
  • Step 5: In fixed_function, we manage our cargo better, preventing any leaks.
  • Step 6: We take another snapshot to confirm that our fix worked.

Expected Output (Visualized in Table Format):

Step Action Memory Used
3 Before Fix High Memory Usage (e.g., 1024 KB)
6 After Fix Reduced Memory Usage (e.g., 512 KB)

Real-World Example:

Think of a memory leak like a dripping faucet in your home. At first, it’s just a drop here and there. But over time, it can fill buckets, wasting water and potentially causing damage. In Python, unmanaged memory is like those wasted drops of water, and it’s our job to fix the leak to keep our program (or home) in tip-top shape.

Interactive Mini-Project:

Let’s make this more fun! Try modifying the leaky_function to create different types of memory leaks. Experiment with different types of data structures or loops. Then, use what you've learned to fix those leaks. It's like playing detective on your ship, finding and fixing leaks of all kinds.

Challenge Steps:

  1. Modify leaky_function with different data structures (like dictionaries or sets) and see how they affect memory usage.
  2. Use tracemalloc to identify the leaks.
  3. Apply your new knowledge to fix these leaks.

What You’ll Learn: This mini-project will deepen your understanding of how different coding practices can lead to memory leaks and how to be vigilant in preventing them. It’s a valuable skill, much like being a savvy ship captain who keeps their vessel in pristine condition.

Optimizing Memory Usage in Python: Becoming a Memory Maestro

Imagine you’re a magician, but instead of pulling rabbits out of hats, you’re skillfully managing the memory in your Python programs. Just like a magician carefully plans their tricks to amaze the audience, we need to plan our code to optimize memory usage. Let’s roll up our sleeves and become memory maestros!

What’s the Trick We’re Going to Perform?

We’ll write a simple Python script that initially uses more memory than necessary. Then, like a magician perfecting their act, we’ll tweak it to optimize memory usage. This is like swapping a heavy prop for something lighter but equally effective in a magic trick.

Coding Example: Memory Optimization Magic

import sys

# Step 1: Create a memory-heavy list
heavy_list = list(range(10000))
print(f"Size of heavy_list: {sys.getsizeof(heavy_list)} bytes")

# Step 2: Optimize by using a generator expression
lighter_list = (x for x in range(10000))
print(f"Size of lighter_list: {sys.getsizeof(lighter_list)} bytes")

Explanation:

  • Step 1: We create a big list called heavy_list that takes up a lot of memory. This is like a magician using a heavy, cumbersome prop.
  • Step 2: We replace the list with a generator expression, lighter_list, which is a much more memory-efficient way to handle large data sets in Python. This is like the magician swapping the heavy prop for something lighter and more elegant.

Expected Output (Visualized as a Table):

List                 Memory Usage
heavy_list High (e.g., 80000 bytes)
lighter_list Low (e.g., 88 bytes)

Real-World Example:

Think of it like packing for a vacation. Initially, you might throw everything you think you need into your suitcase, making it heavy and cumbersome (just like heavy_list). But then, you rethink and pack more smartly, choosing items that are versatile and take up less space (lighter_list), making your travel much easier and more efficient.

Interactive Mini-Project:

Why not try creating your own memory-heavy code and then optimizing it? Here’s a fun challenge: write a script that creates a large data structure, measure its memory usage, and then find ways to reduce that usage.

Challenge Steps:

  1. Write a Python script that creates a large list or dictionary.
  2. Measure its memory usage.
  3. Refactor the code to optimize memory usage, perhaps using generators or other techniques.

What You’ll Learn: This mini-project will teach you the importance of being mindful about memory usage in Python. You’ll learn practical ways to make your code leaner and more efficient, just like how a magician streamlines their tricks for the best performance.

Additional Resources for Mastering Pandas:

To further your journey with Pandas, here are some invaluable resources. Each offers unique insights and practical examples to deepen your understanding and skills:

  1. Understanding DataFrames in Pandas: Explore the backbone of Pandas data manipulation with this guide on DataFrames.
  2. Pandas DataFrame Operations for Beginners: Get started with essential DataFrame operations through this beginner’s guide.
  3. How to Drop a Column in Python: Learn the nuances of streamlining your data with this tutorial on dropping columns.
  4. Mastering Pivot Tables in Pandas: Dive into the world of pivot tables for advanced data analysis with this comprehensive resource.
  5. A Complete Pandas Guide: For an all-encompassing look at Pandas, including vectorization, check out this detailed guide.
  6. Plotting Histograms with Pandas: Enhance your data visualization skills by learning to plot histograms in Pandas.
  7. Real-World Data Analysis with Pandas: Apply your Pandas skills to real-world scenarios with these practical examples.

Each of these resources provides unique insights and practical examples, making them perfect for both beginners and seasoned data analysts. Happy learning! ???

Conclusion: Mastering the Art of Python Memory Management

So, we’ve been on quite a journey through the world of Python’s memory management, haven’t we? It’s like we’ve been on a treasure hunt, uncovering the secrets of how Python handles the precious resource that is memory. Let’s take a moment to look back at the map and recap the treasures we’ve found:

  1. Reference Counting: Remember our analogy of the party and keeping track of the cookies? That’s how Python keeps an eye on its objects. When nobody needs an object anymore, Python knows it’s time to clean up.
  2. Garbage Collection: This is our beach party cleanup crew, taking care of things that reference counting misses, especially those tricky circular references.
  3. Preventing Memory Leaks: Just like fixing leaks in a ship, we learned how to spot and patch up memory leaks in our code to keep our Python program sailing smoothly.
  4. Optimizing Memory Usage: We turned into memory magicians, learning tricks to handle large amounts of data without overburdening our program’s memory.

Understanding these concepts isn’t just academic; it’s a crucial part of being a responsible Python developer. Just like a ship’s captain must understand how to keep their vessel seaworthy, a Python developer should know how to keep their code efficient and effective.

A Parting Piece of Advice

If there’s one thing to take away from our adventure, it’s this: Always be curious and keep exploring. The world of Python is vast and full of mysteries waiting to be solved. Don’t stop at what we’ve covered here. Try out different techniques, experiment with your code, and see what works best for you. There’s no one-size-fits-all solution in programming, especially when it comes to something as nuanced as memory management.

Originally published at https://codewithnazam.com on January 15, 2024.

--

--

Muhammad Nazam
Muhammad Nazam

"As a Full Stack Developer with 7 years of experience, I have a strong background in various programming languages such as Python, JavaScript, Java, and more