Lion Cipher Lab

Read Python bytecode with dis, and take apart a .pyc file by hand

Why it matters

Python feels like a language with no compiled form, but every time you import a module CPython turns it into bytecode and saves it as a .pyc file in a __pycache__ folder. That file is what really runs. Two beliefs about it cause real trouble. The first is 'if I ship only the .pyc, my secrets and logic are hidden'. They are not: strings, numbers and names sit in the file as plain bytes, and the standard library's own dis and marshal modules lay out the logic. The second is 'Python always runs the source I am looking at'. It does not: Python decides whether a .pyc is stale with a cheap check,…

What you'll learn

Steps in this lab

  1. What CPython really runs: source, bytecode, code objects, .pyc
  2. Set up: a terminal, Python, a folder, and a toy licensed app
  3. Read the bytecode with dis
  4. Compile to a .pyc and read the 16-byte header by hand
  5. Unmarshal the code object and walk it
  6. What survives compilation: the canary, the key, and the 'hidden' message
  7. Measure stale-pyc detection: when does Python notice your edit?
  8. Hash-based pycs: checked, unchecked, and what checking costs
  9. Shipping only the .pyc, and why bytecode is tied to one Python version
  10. Honest negatives: what this does not prove, and where it bites
  11. Do it better, then clean up

Open the full interactive lab →