Skip to main content
Ai promptsMigration promptsPythonRefactor prompts

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.

PPromptsCart Team·August 13, 2026·Updated August 13, 2026·7 min read

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/bytes site 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/future shims 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.

Mechanical is safe; semantic is where data breaks

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

VariableRequiredWhat it is
{{python2_source}}YesThe Python 2 file or files to convert
{{encoding_context}}RecommendedHow strings and bytes are used
{{dual_version}}OptionalWhether 2/3 compatibility is needed

Getting started

  1. Copy the structure into ChatGPT, Claude, or Gemini.
  2. Paste a module into {{python2_source}}.
  3. Describe the string and byte usage in {{encoding_context}}.
  4. Run it and read the semantic flags before accepting any edit.
  5. Resolve each encoding and division flag by hand.
  6. 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
Skip the setup

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.

Get the Cross-Language Refactor Harness

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
FAQ

Common questions

Doesn't the 2to3 tool already do this?
`2to3` handles the mechanical syntax: print statements, `dict.items()`, exception syntax. It can't reason about semantics. The str-versus-bytes split, integer division behavior, and changed iteration order need judgment about intent, and that's where a Python 2 to 3 migration prompt earns its place. The prompt separates what a tool can safely automate from what a human has to decide.
What are the dangerous Python 2 to 3 changes?
The silent ones. `str`/`bytes` became distinct types, so code that mixed them in Python 2 fails or corrupts data in Python 3. Integer division changed from floor to true division. Dictionary and `map`/`filter` results became lazy. These don't error at conversion time; they produce wrong results at runtime. The prompt flags each one as semantic, not mechanical.
Which model handles the str/bytes split better?
Claude tends to flag str/bytes ambiguity and ask which the code intends rather than guessing; GPT-4o converts faster but will pick one interpretation silently unless the 'flag semantic changes, don't resolve them' rule is on the final line. Both should defer to the manual-review list on encoding decisions. Pin the model version, since a converter that started resolving ambiguity silently after an update is a data-corruption risk.
Stop reading. Start shipping.

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.