Skip to Content
Course content

264: The difflib Module for Comparing Sequences

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you've just handed a technical spec to a colleague for review. They send it back, but instead of just saying "I fixed some typos," they've given you a "redline" version. You know the drill: there are strikethroughs for things they deleted, underlines for things they added, and the rest of the text remains untouched. You don't have to re-read the whole document; you just scan for the markings to see exactly how the document evolved.

That's exactly what the difflib module does for your data. Whether you're comparing two versions of a configuration file, two lists of usernames, or two strings of code, difflib identifies the delta—the difference—between them. In Python, the "redlines" are represented by specific prefixes: - for things in the first sequence but not the second, + for things in the second but not the first, and a blank space for things that are identical in both.

Getting the "Redline" View with ndiff

When I need a quick, human-readable comparison of two pieces of text, I usually reach for ndiff. It treats the sequences as lists of strings (usually lines in a file) and tells you exactly what happened line-by-line.

import difflib

old_config = [
    "timeout = 30",
    "retry_limit = 3",
    "log_level = DEBUG",
    "enabled = True"
]

new_config = [
    "timeout = 60",
    "retry_limit = 3",
    "log_level = INFO",
    "enabled = True",
    "cache_size = 512"
]

diff = difflib.ndiff(old_config, new_config)
print('\n'.join(diff))

If you run this, you'll see that timeout and log_level are marked with a - and then a +. That's difflib's way of saying "this line was replaced." The cache_size line just gets a + because it's entirely new. I find this far more useful than a simple == check, which just tells you "these aren't the same" without explaining why.

Measuring Similarity with SequenceMatcher

Sometimes you don't need a line-by-line breakdown; you just want to know "how close" two sequences are. This is where SequenceMatcher comes in. It calculates a ratio between 0 and 1. I've used this in the past to build basic "did you mean?" functionality for command-line tools when a user typos a command.

from difflib import SequenceMatcher

string_a = "The quick brown fox jumps over the lazy dog"
string_b = "The quick brown fox leapt over the lazy dog"

matcher = SequenceMatcher(None, string_a, string_b)
print(f"Similarity: {matcher.ratio():.2%}")

The None argument is for a "junk" function—you can actually tell Python to ignore things like spaces or tabs if they aren't important to your comparison. For most of your work, leaving it as None is perfectly fine.

Generating Git-Style Patches

If you've ever looked at a .patch file or a Git diff, you've seen "unified diffs." They're much more compact than ndiff because they omit the parts of the file that didn't change, showing only a few lines of context around the edits. In a massive 1,000-line file, you don't want to scroll through 990 unchanged lines just to find one typo fix.

import difflib

text1 = ["Line 1\n", "Line 2\n", "Line 3\n", "Line 4\n"]
text2 = ["Line 1\n", "Line 2 modified\n", "Line 3\n", "Line 5\n"]

# unified_diff returns a generator, so we cast it to a list or join it
result = difflib.unified_diff(text1, text2, fromfile='original.txt', tofile='modified.txt')

print(''.join(result))

Notice that I added \n to the strings. unified_diff expects the strings to include their newline characters to format the output correctly. If you're reading lines directly from a file using readlines(), you're already covered. If you're creating lists manually, don't forget those newlines, or the output will look like a jumbled mess.




📋 Practical Task

Build a Software Version Change Logger

You are tasked with creating a tool that compares two versions of a requirements.txt file (represented as lists of strings) and generates a summary of the changes. Your script should:

  • Take two lists: old_requirements and new_requirements.
  • Use difflib.ndiff to find the differences.
  • Parse the results to print a user-friendly summary in the following format:
    • "Added: [package_name]"
    • "Removed: [package_name]"
    • "Changed: [old_package] → [new_package]"

Starter Data:

old_requirements = ["flask==2.0.1", "requests==2.25.1", "numpy==1.20.0"]
new_requirements = ["flask==2.1.0", "requests==2.25.1", "pandas==1.3.0"]

Hint: When iterating through ndiff, remember that a "change" is usually represented by a line starting with - immediately followed by a line starting with +.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.