Write a Python 2 to 3 Migration Prompt That Separates Syntax From Semantics
Build a Python 2 to 3 migration prompt that splits mechanical syntax from semantic changes, flags what 2to3 can't fix, and locks a per-file contract.
The reason Python 2 to 3 migrations drag on for years isn't the print statements. Those convert in seconds. It's the str/bytes split, the integer division change, the lazy iterators, the encoding assumptions baked into code that nobody documented. A Python 2 to 3 migration prompt is worth building precisely because it separates the mechanical changes a tool can handle from the semantic ones that quietly corrupt data if you get them wrong.
Search results for this are either the 2to3 tool docs or a single generic "convert this script" prompt. Neither distinguishes safe from dangerous. They mechanically rewrite syntax and leave the semantic landmines for you to step on at runtime.
Why 2to3 finishes 80% and leaves the scary 20%
2to3 and futurize are good at what they do. Print statements, exception syntax, renamed modules. They rewrite the surface and the code parses under Python 3. Then it runs, and a function that concatenated a string and a byte string in Python 2 either throws or, worse, writes garbage to a file.
The stance: the mechanical 80% was never the risk. The risk is the 20% that doesn't error, it just behaves differently. 5 / 2 was 2 in Python 2 and is 2.5 in Python 3. A migration prompt that rewrites syntax and stays silent on semantics is doing the safe part and skipping the part that actually breaks production.
What a Python 2 to 3 migration prompt does
A Python 2 to 3 migration prompt is a triage converter that reads Python 2 source, applies the mechanical changes, and separates them from semantic changes it flags for human review against a locked contract. It tells you what's safe and what isn't.
Run it to:
- Apply mechanical syntax fixes (print, exceptions, renamed imports)
- Flag every
str/bytessite that needs an encoding decision - Mark integer-division changes where behavior shifts
- Identify lazy iterators (
map,filter,dict.keys) that code consumed twice - Note
six/futureshims where dual-version support is wanted - Lock a per-file contract that tags each edit safe or review-needed
That safe-versus-review tag is what turns a risky rewrite into a reviewable one.
Anatomy of the prompt
Variables
{{python2_source}} → the Python 2 file(s) to convert
{{encoding_context}} → how strings/bytes are used (files, network)
{{dual_version}} → whether 2/3 compatibility is needed (six/future)
Prompt
Role: you are a Python 2-to-3 migration engineer.
Task: convert {{python2_source}} to Python 3.
Rules:
- Apply mechanical fixes; tag them safe.
- Flag str/bytes, division, and lazy-iterator changes as semantic.
- Never resolve an encoding ambiguity; route it to review.
Output contract
Return: converted file + per-edit safe/review tag + semantic flags.
1. Give it the encoding context
The str/bytes decision depends on what the code does with the data. Paste the file or network usage into {{encoding_context}} so the model can flag, not guess, the encoding boundary.
2. Decide on dual-version support
Set {{dual_version}} if the code has to run on both Python 2 and 3 for a transition period. That changes whether the model reaches for six shims or clean Python 3.
3. Work the semantic-flag list
The mechanical edits you can mostly trust. The semantic flags, division, encoding, lazy iteration, are your review queue. Each one is a place the behavior could shift.
The trap in a Python 2 to 3 migration is treating every change as equal. Converting print x to print(x) is safe and boring. Converting code that assumed str was bytes is a data-corruption risk that won't error. A migration prompt has to tag the difference, so your review time goes to the encoding decisions and the division changes, not to re-reading print statements.
Prompt-craft patterns for version migration
Tag every edit safe or review-needed. The output contract's most useful field isn't the code, it's the tag. A mechanical fix you can skim; a semantic flag you have to read. Force the model to classify each change.
Never resolve an encoding ambiguity. When the model can't tell whether something should be str or bytes, the right move is to flag it, not to pick. A silent choice here corrupts data. Make the contract forbid silent resolution.
Surface the lazy-iterator gotcha. map and filter return iterators in Python 3, consumable once. Code that iterated a map result twice worked in Python 2 and breaks in Python 3. The prompt should flag double-consumption sites.
Where Claude and GPT-4o diverge on semantics
The models differ most on ambiguity. Claude tends to flag a str/bytes site and state both interpretations rather than committing to one, which is the safe behavior for a data-integrity change. GPT-4o converts faster and will silently pick an interpretation unless the "flag semantic changes, don't resolve them" rule is the last instruction it reads.
Both models reliably handle the mechanical syntax, so the value is entirely in how they treat the dangerous 20%. Integer division is a quiet one: neither model will warn you that / changed meaning unless asked, so the contract has to name it. Restate the semantic-flag rule for GPT-4o on the final line; pin the model version, because a converter that began resolving encoding ambiguity on its own after an update is exactly the kind of change that corrupts a file before anyone notices.
How this fits broader migration work
A Python version upgrade is one transformation among many. The triage discipline, separate what's safe to automate from what needs judgment, applies to any port. For language-to-language work, the cross-language refactor prompt handles idiom preservation; for a staged rollout across services, the framework migration prompt covers reversible steps.
Variables you'll set
| Variable | Required | What it is |
|---|---|---|
{{python2_source}} | Yes | The Python 2 file or files to convert |
{{encoding_context}} | Recommended | How strings and bytes are used |
{{dual_version}} | Optional | Whether 2/3 compatibility is needed |
Getting started
- Copy the structure into ChatGPT, Claude, or Gemini.
- Paste a module into
{{python2_source}}. - Describe the string and byte usage in
{{encoding_context}}. - Run it and read the semantic flags before accepting any edit.
- Resolve each encoding and division flag by hand.
- Run the file under Python 3 and check the lazy-iterator sites.
For a migration spanning many modules with the triage and staging built in, the Cross-Language Refactor Harness runs the contract-locked pass across the codebase.
Browse the migration prompt packs →The Cross-Language Refactor Harness does this end-to-end: it preserves idioms, flags non-translatable constructs, and locks an output contract per file, so a Python 2-to-3 pass separates the mechanical edits from the str/bytes and division changes that need a human instead of resolving them silently. It's part of The Complete AI Prompts Bundle, a one-time lifetime license to the whole catalog plus future packs, worth it if you run more than one migration a year.
If your Python upgrade also touches the database layer, the schema side needs its own safety net. The Database Migration Safety Harness flags destructive operations and stages a reversible rollout. New to buying packs versus rolling your own? Start with how to choose a reusable AI prompt pack.
See the Database Migration Safety Harness →Common questions
Doesn't the 2to3 tool already do this?
What are the dangerous Python 2 to 3 changes?
Which model handles the str/bytes split better?
Get the prompt packs this guide is built on
Ready-to-paste prompts with documented variables and usage guides for ChatGPT, Claude, and Gemini. One-time payment, own it forever.
More prompt guides

Gemini vs Claude for Long-Context Code: Window or Accuracy
The honest framing of Gemini vs Claude for long-context code isn't which model is smarter. It's a tradeoff between two different things: how much code you can fit in one prompt, and how often the mode…

An OKR Drafting Prompt That Catches Vanity Key Results
An OKR drafting prompt has to fight the model's strongest instinct: handing back something that sounds like a goal but can't be measured. Ask any model for key results and you'll get "increase user en…

A Technical Design Doc Prompt That Holds the RFC Structure
A technical design doc prompt earns its keep when it stops every author from inventing a new doc structure. Context, the options you considered, why you picked one, what breaks, how you roll it out. S…