---
slug: 01a-python-to-synsema
title: Python → Synsema
description: La tabla de traducción — escribí Synsema traduciendo el Python que ya sabés, con cada divergencia semántica marcada y doctesteada.
example_ids: [python-diff]
---

# Python → Synsema — la tabla de traducción

Vos (un LLM, o un dev de Python) ya sabés Python. Esta página mapea el reflejo de Python a
la forma Synsema y marca exactamente dónde diverge la semántica. Es más rápido que aprender
de cero, y previene el modo de falla clásico: escribir Python con keywords de Synsema.

**La regla que previene la mayoría de las alucinaciones: si no lo viste en estas docs, no
existe.** No hay `import`, no hay stdlib de Python, no hay clases, no hay comprehensions,
no hay decoradores, no hay `with`, no hay generadores, no hay sintaxis de método sobre
valores (`xs.append(x)` → los builtins son tasks planas: `append(xs, x)`).

Cada afirmación semántica de abajo está asegurada por este doctest que pasa:

```synsema
-- Doc example: the Python → Synsema divergences that bite the hardest.
-- Every claim in the translation-table page is asserted here (doctested).
intent: "doc example: python-to-synsema translation table"

task boom()
    raise("kaput")

task reraise_error()
    try
        boom()
    recover err
        raise(err)

task bad_return()
    return 5

task give_none()
    give None

task iterate_map_directly()
    each k in {"a": 1}
        print(k)

test "assignment is let/set, not = (x = 5 is a parse error)"
    let x be 1
    set x to 2
    assert_eq(x, 2)

test "Python 'return'/'None'/'True' PARSE but are undefined names — use give/nothing/true"
    assert_error(bad_return)
    assert_error(give_none)
    assert_eq(nothing == nothing, true)

test "try/recover (not try/except); recover SWALLOWS unless you raise(err)"
    let seen be ""
    try
        boom()
    recover err
        set seen to err
    assert(contains(seen, "kaput"))
    assert_error(reraise_error)

test "and/or short-circuit (engine v0.6.10+) and always yield a bool"
    let m be {"a": 1}
    assert_eq(contains(m, "b") and m["b"] == 1, false)
    assert_eq(contains(m, "a") and m["a"] == 1, true)
    assert_eq(contains(m, "a") or m["zz"] == 1, true)
    assert_eq(1 or 0, true)

test "append returns a NEW list (no mutating .append); reassign with set"
    let xs be [1, 2]
    let ys be append(xs, 3)
    assert_eq(xs, [1, 2])
    assert_eq(ys, [1, 2, 3])

test "f-string equivalent is the backtick string; quoted strings stay literal"
    assert_eq(`n={1 + 1}`, "n=2")
    assert(contains("n={1+1}", "{"))

test "each cannot iterate a map — go through keys()"
    assert_error(iterate_map_directly)
    let ks be []
    each k in keys({"x": 1, "y": 2})
        set ks to append(ks, k)
    assert_eq(ks, ["x", "y"])

test "comprehension equivalent: apply + where; slicing is slice()"
    assert_eq(apply((x) => x * 2, where([1, 2, 3], (x) => x > 1)), [4, 6])
    assert_eq(slice([10, 20, 30, 40], 1, 3), [20, 30])
    assert_eq(slice([10, 20, 30, 40], -2, 4), [30, 40])

test "the `in` operator is contains(); on maps it checks KEYS"
    assert(contains([1, 2], 2))
    assert(contains("abc", "b"))
    assert(contains({"a": 1}, "a"))

test "text + number CONCATENATES (unlike Python); text * n does not exist"
    assert_eq("a" + 1, "a1")
    assert_error(() => "ab" * 2)

test "len/str/sorted are length/text/sort_by; d.get(k, default) does not exist"
    assert_eq(length("abc"), 3)
    assert_eq(text(42), "42")
    assert_eq(sort_by([3, 1, 2], (x) => x), [1, 2, 3])
    assert_error(() => get({"a": 1}, "a"))
    assert_error(() => {"a": 1}["b"])

test "enumerate gives {index, item} maps (Python's for i, x in enumerate)"
    let e be enumerate(["a", "b"])
    assert_eq(e[0].index, 0)
    assert_eq(e[1].item, "b")
    assert_eq(length(enumerate([])), 0)
```

## Reflejos de sintaxis

| En Python | En Synsema | ⚠️ Divergencia |
|---|---|---|
| `x = 5` … `x = 6` | `let x be 5` … `set x to 6` | `x = 5` → error de parseo `Unexpected token: ASSIGN ('=')`. `=` existe SOLO en params con default / args nombrados: `task f(x, y = 1)`, `f(x, y = 2)` |
| `# comentario` | `-- comentario` | `#` → `Unexpected character: '#'` |
| `if / elif / else:` | `when / otherwise when / otherwise` (sin dos puntos) | un `:` al final → error de parseo; `elif` no es una palabra |
| `x if c else y` | `when c then x otherwise y` | forma expresión inline, usable en `let`/args |
| `for x in xs:` | `each x in xs` | `for` → error de parseo |
| `for k in un_dict:` | `each k in keys(m)` | **`each` no puede iterar un map**: `Cannot iterate over map`. Pasá por `keys(m)`/`values(m)` |
| `for i, x in enumerate(xs):` | `each e in enumerate(xs)` … `e.index` / `e.item` | `enumerate(list)` → `[{index, item}, …]` |
| `while c:` | `while c` | mismo keyword, sin dos puntos; un loop desbocado corta con `Loop exceeded maximum iterations` |
| `def f(x): return v` | `task f(x)` … `give v` | `def` → error de parseo. `return` PARSEA como un nombre y falla en runtime: `Undefined variable: 'return'` — la palabra es `give` |
| `lambda x: x + 1` | `(x) => x + 1` | — |
| `None` / `True` / `False` | `nothing` / `true` / `false` | las formas con mayúscula parsean y después fallan: `Undefined variable: 'None'` (igual `True`/`False`) |
| `x is None` | `x == nothing` | no hay operador `is` de identidad (`is` es de `match`) |
| `f"n={n}"` | `` `n={n}` `` (string con backticks) | `f"..."` → error de parseo. **Las strings con comillas `"..."` NO interpolan** (`"{n}"` queda literal) y un newline literal adentro es `Unterminated string` — los backticks hacen ambas cosas |
| `"""multilínea"""` | `` `multilínea` `` | los backticks permiten newlines reales + `{expr}` |
| `[f(x) for x in xs if p(x)]` | `apply(f, where(xs, p))` | la sintaxis de comprehension → error de parseo |
| `xs[1:3]`, `xs[-2:]` | `slice(xs, 1, 3)`, `slice(xs, -2, length(xs))` | `[1:3]` → error de parseo; `slice` acepta negativos estilo Python, funciona en listas/text/bytes |
| `x in xs` (operador) | `contains(xs, x)` | `in` solo vale dentro de `each`. En maps `contains` chequea CLAVES |
| `try/except E as e:` | `try` … `recover err` | `except` → error de parseo. `err` es el TEXTO del mensaje (no hay tipos/jerarquía de excepciones). **`recover` TRAGA por defecto** — re-propagá con `raise(err)` |
| `raise ValueError("x")` | `raise("x")` (o la sentencia `raise "x"`) | un solo tipo de error; en engine ≤ v0.5.1 usá la forma con paréntesis |
| `import json`, `import requests` | nada que importar — los builtins son globales | `import x` parsea como un nombre y falla: `Undefined variable: 'import'`. JSON/HTTP/etc. son builtins gateados por capabilities (abajo) |
| `from mimodulo import f` | `use "./mimodulo.syn" as m` … `m.f()` | solo módulos `.syn` locales; los exports necesitan `export` — [Módulos](/es/0.6.x/14-modules) |
| `class Person:` | `type Person` (campos) + tasks planas | sin métodos/herencia/`self`; construí `Person("Alice", 30)`, accedé `p.name` / `name of p` / `p["name"]` |
| `match/case` | `match` … `is patrón` | los brazos usan `is`, el default es `otherwise` — [Sintaxis](/es/0.6.x/10-syntax) |

Además: las palabras LLM **`reason` / `decide` / `analyze` / `generate` son reservadas en
todas partes** (incluso como nombres de member/param) — `let reason be 1` → `'reason' is a
reserved word in Synsema`. Nombrá las cosas `resolve`, `why`, etc.

## Equivalentes de builtins (los métodos son tasks planas)

| En Python | En Synsema |
|---|---|
| `len(x)` | `length(x)` (text/list/map/bytes/array) |
| `str(x)` / `int(s)` / `float(s)` | `text(x)` / `number(s)` (siempre float; `floor()` para entero) |
| `xs.append(x)` (muta) | `append(xs, x)` → **devuelve una lista NUEVA**; reasigná: `set xs to append(xs, x)` |
| `s.upper()` / `s.lower()` / `s.strip()` | `upper(s)` / `lower(s)` / `trim(s)` |
| `s.split(",")` / `",".join(xs)` | `split(s, ",")` / `join(xs, ",")` |
| `s.startswith(p)` / `s.replace(a, b)` | `starts_with(s, p)` / `replace_text(s, a, b)` |
| `sorted(xs, key=f)` / `reverse=True` | `sort_by(xs, f)` / `sort_by(xs, (x) => 0 - x)` (no hay `sort` pelado) |
| `sum(xs)` / `min(xs)` / `max(xs)` | `sum(xs)` / `min(xs)` / `max(xs)` (también variádico `max(a, b, c)`) |
| `map(f, xs)` / `filter(p, xs)` | `apply(f, xs)` / `where(xs, p)` — ambos aceptan cualquiera de los dos órdenes |
| `functools.reduce(f, xs, init)` | `reduce(xs, f, init)` |
| `xs.index(v)` (lanza) / `v in xs` | `index_of(xs, v)` → **`nothing`** si no está (no -1, no error) |
| `d.get(k, default)` | no existe — `when contains(m, "k")` y después indexá (`when` anidado, ver trampas) |
| `d.keys()` / `d.values()` / `d.items()` | `keys(m)` / `values(m)` / no hay `items` — iterá `keys(m)` e indexá |
| `json.dumps(x)` / `json.loads(s)` | `json_encode(x)` / `json_decode(s)` (puros, sin import) — [JSON](/es/0.6.x/36-json) |
| `range(n)` | `range(n)` → una lista real (también `range(a, b, paso)`) |
| `print(...)` | `print(...)` (bufferea bajo `run` hasta salir — `flush()` para salida en vivo) |
| `re.fullmatch` / `re.findall` | `matches(s, pat)` (match COMPLETO) / `find_all(s, pat)` — [Builtins](/es/0.6.x/90-builtins) |
| `open(p).read()` / `requests.get(url)` | `read_file(p)` + `require file(...)` / `fetch(url)` + `require net(host)` — [Archivos](/es/0.6.x/30-io-files), [HTTP](/es/0.6.x/32-http-client) |

## Trampas semánticas — parece Python, se comporta distinto

| Parece que | Lo que pasa en realidad (doctesteado arriba) |
|---|---|
| `a and b` cortocircuita y devuelve el operando (`x or "default"`) | También cortocircuita (engine v0.6.10+) pero **siempre devuelve un bool** — `x or "default"` es `true`/`false`, nunca el default. Usá `when x == nothing` … `set x to "default"` |
| `xs.append` muta in place | `append` (y familia) devuelven valores nuevos; el original queda intacto. Reasigná con `set` |
| `d["missing"]` → KeyError que atrapás por tipo | `Map has no key 'missing'` — atrapable solo como `try/recover` (texto del mensaje) |
| `"a" + 1` → TypeError | **Concatena**: `"a" + 1` → `"a1"` (text + number coerciona). Pero `"ab" * 2` y `1 + true` SÍ son errores — no hay repetición ni aritmética de bools |
| `except:` deja el programa muriendo | `recover` **traga el error entero** (la task termina normal). Para fallar hacia arriba, `raise(err)` dentro de `recover` |
| iterar un dict da las claves | `each` sobre un map es ERROR — usá `keys(m)` |

Más trampas (también verificadas contra el engine): [Contra tus instintos](/es/0.6.x/01-counter-your-priors)
y [Errores y exit codes](/es/0.6.x/92-errors).

## Dónde tu intuición de Python SÍ es segura (verificado — confiá)

- La división siempre devuelve float (`10 / 3` → `3.33…`), como Python 3. División entera: `floor(a / b)`.
- `round()` es redondeo banker's, igual que Python: `round(2.5)` → `2`, `round(3.5)` → `4`.
- Truthiness: `nothing`/`false`/`0`/`""`/`[]`/`{}` son falsy, todo lo demás truthy.
- `[1] + [2]` → `[1, 2]` (concatenación de listas), `slice` acepta índices negativos.
- Los literales de map `{"k": v}` y de lista se ven y anidan como dicts/listas.
- La indentación define bloques (4 espacios), comentarios hasta fin de línea, `and`/`or`/`not` son palabras.

## Sin equivalente en Python — leé la página del tema antes de usar

- **Capabilities**: la I/O es deny-by-default; declará `require net("host")` / `file(...)` /
  `db(...)` / `serve(PUERTO)` / `llm` arriba o las llamadas fallan → [Capabilities](/es/0.6.x/20-capabilities)
- **Ops LLM como keywords**: `decide between [...] given x`, `generate`, `analyze`, `reason` → [Primitivas LLM](/es/0.6.x/50-llm-primitives)
- **Agentes/concurrencia**: `agent` / `spawn` / `share` / `observe` / `signal` / `wait_for`,
  `parallel_map` → [Multi-agente](/es/0.6.x/60-agents)
- **Servidor HTTP como sintaxis**: `serve on 8080` + bloques `route "GET /x"` → [Serve](/es/0.6.x/40-serve)
- **Reflejos de FastAPI**: `@app.post("/x")` + un modelo Pydantic ↔ `route "POST /x"` + `expect body {…}`; `/docs` y `/openapi.json` ↔ las mismas URLs, generadas; `app.openapi()` ↔ `synsema openapi app.syn` → [Construí una API](/es/0.6.x/43-build-api)
- **Secretos**: valores `secret("KEY")` que jamás se imprimen/serializan → [Secretos](/es/0.6.x/21-secrets)
- **Humano en el loop**: `approve` / `confirm` / `ask` / `show` → [Humano en el loop](/es/0.6.x/62-human)
- **Tests en el archivo**: bloques `test "nombre"` + `assert_eq`, corridos por `synsema test` → [CLI](/es/0.6.x/70-cli)
