Skip to main content

Strategy Pattern in Python: Write Flexible, Clean Code

Strategy Pattern in Python: Write Flexible, Clean Code How to pick different algorithms at runtime—without a tangled web of if/else Hey there! Today I want to write about a design pattern:  the Strategy Pattern . Perfect for when you have multiple ways to solve the same problem and you want clean, maintainable code. Why the Strategy Pattern? Let’s start with a scenario: imagine you're building a pizza delivery app, and different restaurants have wildly different APIs. Without planning, you might end up stuffing your code full of branching logic. Instead, the Strategy Pattern lets you isolate each integration into its own “strategy,” making your code neat and flexible. In software design terms, a strategy defines a family of algorithms, each encapsulated in its own class (or callable), and they’re all interchangeable at runtime. This helps avoid massive if/else blocks and makes adding new behavior super easy without touching existing code—hell...

Avoiding Pandas Pitfalls: View vs Copy and Keeping Your Base Dataframe Safe

Avoiding Pandas Pitfalls: View vs Copy and Keeping Your Base DataFrame Safe

Ever accidentally changed your original pandas DataFrame without meaning to? I certainly have. It can feel like a debugging nightmare. In this post, I want to walk you through some of the lessons I’ve learned, like how pandas handles views and copies, and how to organize your code so you don't trip over unexpected behavior.

Understanding View vs Copy in pandas

In pandas, a “view” is just another way of looking at the same data in memory. That means if you change it, the original changes too. A “copy”, on the other hand, is a separate object entirely. What you do with it stays isolated. The problem is that pandas doesn’t always make it obvious whether you're dealing with a view or a copy. Sometimes a slice of a DataFrame gives you a view. Other times it gives you a copy. That inconsistency is the reason behind the infamous SettingWithCopyWarning [1].

For example, if you write something like:

subset = df.loc[df["col"] > 0]
subset["col"] = 5

you might be changing df, or you might not. It depends on how pandas constructed the subset behind the scenes. That’s why pandas often shows a warning here, trying to tell you: “Hey, I’m not sure if you meant to do this, and you might be changing something you didn’t intend to.” [2]

There’s no easy built-in method to always know if you're holding a view or a copy, especially with DataFrames. While the .base attribute can sometimes offer clues with Series, it's not reliable across the board [3]. When in doubt, it's better to assume ambiguity and act accordingly.

Safely Modifying Your Base DataFrame

Assuming you’re working with pandas version 2.0 or later, here’s how I keep my base DataFrame (usually called df or df_base) clean and unchanged unless I explicitly modify it.

I treat df_base as a constant source of truth. It’s the clean slate, and I don’t touch it unless it’s intentional. When I need to make changes to the original data, I always use .loc, for example:

df_base.loc[row_mask, "col"] = new_value

This approach avoids chained‑indexing ambiguity, is unambiguous about modifying the original DataFrame, and prevents warnings [1][7].

Whenever I’m doing exploratory work or data transformations that shouldn’t affect the original, I explicitly make a copy:

working = df_base.copy()

This gives me a separate DataFrame I can manipulate freely [4].

Since pandas 2.0, there’s an even safer and more efficient option: Copy‑On‑Write (CoW). When you enable CoW via:

pd.set_option("mode.copy_on_write", True)

pandas treats operations as though they return copies. Under the hood it may use views until you actually modify the data. At that point it quietly makes a real copy, preserving your base data. You get both performance and safety [5].

One important caveat: CoW doesn’t replace the need for .copy() when passing data between functions or scopes. It ensures safe behavior at runtime but won’t isolate data unless you explicitly copy it [8].

import pandas as pd
pd.set_option("mode.copy_on_write", True)

df_base = pd.read_csv("data.csv")

# Clear modification intent with loc
df_base.loc[df_base["status"] == "old", "status"] = "archived"

# Create an isolated version for transformations
df_work = df_base.copy()
df_work["norm_score"] = (df_work["score"] - df_work["score"].mean()) / df_work["score"].std()

Organizing Code for Clarity

Good naming conventions and a bit of structure go a long way toward avoiding confusion. I usually call my untouched source data df_base. If I’m transforming or cleaning it, I’ll work with a df_work or df_clean. It’s a small habit that really helps future-me (and teammates) understand what's going on at a glance.

Whenever I wrap logic into functions, I make sure each one either copies the data internally or clearly documents that it modifies what it’s given. Here’s a simple example:

def clean_base(df):
    df = df.copy()
    # cleaning logic here
    ...
    return df def analyze(df): return df.describe() df_clean = clean_base(df_base) print(analyze(df_clean))

In this case, clean_base() ensures the original stays safe. The analyze() function assumes it's just reading the data, not altering it. This kind of separation is especially useful when chaining operations or debugging downstream effects.

I also make a habit of leaving quick comments when it’s not obvious whether something is a view or a copy. For example:

# Expect view: this changes df_base directly
df.loc[df["A"] < 0, "A"] = 0

# Safe copy to modify
df_work = df_base.loc[:, ["A", "B"]].copy()

A Few Best Practices

To recap, if you're aiming for safer, clearer pandas code, here are a few habits that make a big difference. Use .copy() anytime you don’t want changes to touch the original data. When you do mean to make a change to the base DataFrame, prefer .loc[…] = …. It’s more explicit and avoids warnings. If you're using pandas 2.0 or later, consider turning on Copy-On-Write mode for more predictable behavior without sacrificing memory efficiency.

Being consistent with naming (calling your datasets base, work, or out) really helps. And wrapping transformations into functions that either copy or document their intent will make your code much easier to maintain.

Example in Practice

Here’s a simple example of how I often structure my pandas scripts to avoid surprises:

import pandas as pd

pd.set_option("mode.copy_on_write", True)

df_base = pd.read_csv("data.csv")

def prepare(df):
    df = df.copy()
    df["score_norm"] = (df["score"] - df["score"].mean()) / df["score"].std()
    return df

def flag_high_score(df):
    df.loc[df["score_norm"] > 2, "flag"] = True
    return df

df_work = prepare(df_base)
df_out = flag_high_score(df_work)

# df_base is unchanged
print(df_base.head())
print(df_out.head())

What’s nice here is that it’s very clear what’s happening. The prepare() function creates a clean working copy. The flag_high_score() function mutates the passed-in DataFrame intentionally. The original data, df_base, stays untouched the entire time.

Final Thoughts

Pandas is a powerful tool, but its view and copy behavior can cause subtle bugs if you’re not careful. Thankfully, with a few small habits like naming your variables clearly, copying data when needed, and using .loc for intentional changes, you can avoid the most common traps. If you’re using pandas 2.0 or newer, enabling Copy-On-Write mode is an easy win that makes things even more predictable.

I hope this post helps you write cleaner, safer data code and gives you one less thing to worry about in your workflow.

🔗 References

  1. Real Python – Understanding SettingWithCopyWarning in pandas
  2. Stack Overflow – pandas view vs copy: how do I tell?
  3. Stack Overflow – Checking view vs copy via internal attributes
  4. Stack Overflow – Using `.copy()` to isolate working DataFrame
  5. Pandas – Copy‑On‑Write guide (pandas ≥ 2.0)
  6. Practical Data Science – Views and Copies in pandas
  7. Stack Overflow – Enabling CoW eliminates chained‑assignment risks
  8. Pandas – CoW reference behavior and limitations

Comments

Popular posts from this blog

Format Your Python Code with black (Using uv like a Pro)

Keep things clean, fast, and simple—no extra installs needed.   Hey there! I want to show you a smooth way to clean up your Python code using black , but with a twist: we’ll use uv to handle it. Why uv you might ask? My short answer: No cluttered virtual environments, no extra installs, just clean code. Why I Use uv (and you might too) If you’ve ever felt bogged down setting up virtual environments just to install tools like black , flake8 , or pytest , I hear you. uv is a package and project manager that’s fast, one-and-done, and smart (and written in Rust , by the way). It lets you run or install tools in clean, cache-friendly environments, with commands like: uv tool install black # keeps black handy on your PATH uvx black my_code.py # quick run in a temp environment Check out the uv docs, especially the "Tools" section, for all the details ( Astral Docs ). Step‑by‑Step: Get black Set Up 1. Install uv (from the  Astral Doc...

Understanding the Python GIL (for Beginners)

What it is, why it matters, and what’s coming in future Python releases. Hey! Let’s talk about something that trips up a lot of developers when they dive into Python threading: the Global Interpreter Lock , or GIL . We will cover the essentials here. This will be a good starting point to decide if you want or need to dig deeper in the topic. What is the GIL? The GIL is a lock inside CPython (the standard Python interpreter). It ensures only one thread executes Python bytecode at a time, even on multi-core machines. See more on Wikipedia and the Python Wiki . It was put in place to manage Python’s internal systems (like memory and reference counting) without race conditions. Without the GIL, Python could crash or corrupt data. That’s why it exists, even if it feels limiting [ 1 ]. How does the GIL affect your code? Single-threaded scripts: No change. The GIL is invisible. I/O-bound tasks (like networking or file access): The G...