skills/verification/python-to-lean4-translator/SKILL.md
Translates Python into Lean 4 for interactive theorem proving, handling dynamic types and duck typing by specializing to the concrete types actually used. Use when proving correctness of a Python algorithm beyond what testing can establish, or when building a verified reference for numerical or combinatorial Python code.
npx skillsauth add santosomar/general-secure-coding-agent-skills python-to-lean4-translatorInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This is a delta over → c-cpp-to-lean4-translator — same target, easier memory model (no pointers), harder type discipline (dynamic → dependent).
| Python | Lean |
| ----------------------------- | ------------------------------------------------------------------ |
| int | Int — both unbounded. The easy case. |
| list[T] | List T — both immutable-by-default conceptually. Pythonic .append in a loop → build via :: then reverse, or fold directly. |
| dict[K,V] | Std.HashMap K V or K → Option V function — the function form proves better |
| tuple | Prod (×) for pairs; anonymous structure for n-tuples |
| None / Optional[T] | Option T — direct |
| for x in xs: | xs.foldl, xs.map, or explicit recursion — same as C→Lean |
| List comprehension | xs.filter p \|>.map f — reads almost the same |
| range(n) | List.range n : List Nat |
| f-string / str | String — but if you're proving things about strings, use List Char for induction |
| @dataclass | structure — direct |
| raise / try | Except ε α monad — or precondition the error away |
Python functions often accept "anything duck-typed like X." Lean can't verify "anything." Narrow:
def per type, or (b) a typeclass capturing the shared interface.def sum_sq(xs): # accepts list of "numbers"
return sum(x*x for x in xs)
Call sites only ever pass list[int] → translate for Int:
def sumSq (xs : List Int) : Int := (xs.map (· ^ 2)).sum
If floats also appear → either two defs, or [Mul α] [Add α] [Zero α] constraints.
Python:
def is_palindrome(s: str) -> bool:
i, j = 0, len(s) - 1
while i < j:
if s[i] != s[j]:
return False
i += 1; j -= 1
return True
Lean:
def isPalindrome (s : List Char) : Bool :=
s = s.reverse
-- The imperative two-pointer version, via recursion:
def isPalindrome' : List Char → Bool
| [] => true
| [_] => true
| c :: cs =>
match cs.getLast? with
| none => true
| some d => c = d && isPalindrome' cs.dropLast
-- Prove they agree (connects the efficient form to the spec form):
theorem isPalindrome'_eq (s : List Char) :
isPalindrome' s = isPalindrome s := by
induction s using List.strongRecOn with
| _ s ih =>
match s with
| [] => rfl
| [_] => simp [isPalindrome, isPalindrome']
| c :: d :: rest => sorry -- unfold, use ih on dropLast, List.reverse lemmas
Strategy: Translate twice — once as the obvious spec (s = s.reverse), once as the imperative algorithm (the two-pointer walk). Then prove they agree. The spec is what you trust; the algorithm is what runs.
| Python feature | Don't translate — instead |
| ----------------------------- | ---------------------------------------------------------- |
| print, open, requests | These are effects. Verify the pure core; trust the I/O. |
| *args, **kwargs | Fix the arity to what the call site uses. |
| Metaclass / __getattr__ | If the algorithm depends on this, Lean is the wrong tool. |
| Float (0.1 + 0.2) | Lean's Float is IEEE but proving things about it is painful. If the math is real-valued, use ℝ from Mathlib and accept the model gap. |
partial def to dodge termination. You lose induction and all the proof infrastructure. Find the decreasing measure.list.append in a loop as xs := xs ++ [x] inside a fold — that's O(n²). Fold into a reversed list and .reverse once at the end, or use Array with push.normalize() means, ask — a proof against a guessed spec is a proof of nothing.Same as → c-cpp-to-lean4-translator, plus:
## Type narrowing
Python accepts: <duck-typed description>
Translated for: <concrete type(s)> — justified by <call-site analysis | type hints>
development
Extracts human-readable pseudocode from a verified formal artifact (Dafny, Lean, TLA+) while preserving the verified properties as annotations, so the proof-carrying logic can be reimplemented in a production language. Use when porting verified code to an unverified target, when documenting what a formal spec actually does, or when handing a verified algorithm to an implementer.
development
Translates natural-language or pseudocode descriptions of concurrent and distributed systems into TLA+ specifications ready for the TLC model checker. Identifies state variables, actions, type invariants, safety properties, and liveness properties from the description. Use when formalizing a protocol, when the user describes a distributed algorithm to verify, when designing a consensus or locking scheme, or when starting formal verification of a concurrent system.
testing
Reduces a TLA+ model so TLC can actually check it — shrinks constants, adds state constraints, abstracts data, or applies symmetry — when the state space is too large to enumerate. Use when TLC runs out of memory, when checking takes hours, or when a spec works at N=2 and you need confidence at larger scale.
development
TLA+-specific instance of model-guided repair — reads a TLC error trace, identifies the enabling condition that should have been false, strengthens the corresponding action, and maps the fix to source code. Use when TLC reports an invariant violation or deadlock and you have the code-to-TLA+ mapping from extraction.