PythonU — Comprehensive Comparison

Side-by-side for Python developers. U keeps what works — Python method names, familiar iteration, clean lambdas — and adds structural enforcement: modifiers instead of type hints, compile-time null safety, no data races by default.

01

Variables & Types

Declaration & inference

Python
# no annotation — type inferred
name = "Alice"
count = 0
ratio = 3.14

# optional type hints (not enforced)
name: str = "Alice"
items: list[int] = []
U
// type inferred at declaration
name = "Alice"
count = 0
ratio = 3.14

// explicit annotation (compiler enforces)
name: S = "Alice"
items: [I] = []
Key difference: Python type hints are documentation. U annotations are enforced by the compiler — wrong type is a compile error, not a runtime surprise.

Mutability

Python
# everything mutable by default
x = 5
x = 10  # fine

# immutable requires effort
from typing import Final
X: Final = 5
X = 10  # mypy error, not runtime
U
// immutable by default (-M)
x: I = 5
x = 10  // compile error: -M

// opt into mutability
x: I +M = 5
x = 10  // fine: +M declared
U defaults: scalars (I, N, S) are -M (immutable). Collections and heap objects ([T], {K:V}, d Class) are +M(MVCC) by default — mutable with MVCC write policy. Change the policy explicitly: +M(Actor), +M(Mutex), or -M for immutable heap.
02

Functions

Declaration & return

Python
def add(a: int, b: int) -> int:
    return a + b

def greet(name: str) -> str:
    return f"Hello, {name}"
U
f add(a: I, b: I) -> I
    r => a + b

f greet(name: S) -> S
    r => "Hello, " + name

Default arguments

Python
def connect(host: str, port: int = 5432) -> Connection:
    ...
U
f connect(host: S, port: I = 5432) -> Connection
    ...

Multiple return values

Python
def minmax(data: list[float]) -> tuple[float, float]:
    return min(data), max(data)

lo, hi = minmax([1, 5, 3])
U
f minmax(data: [N]) -> {lo: N, hi: N}
    r => { lo: data.min(), hi: data.max() }

result = minmax([1.0, 5.0, 3.0])
result.lo  // 1.0
U returns named structs rather than positional tuples — fields are always named, never positional. No more result[0] vs result[1] confusion.

Compile-time functions

Python
# no direct equivalent
# closest: dataclasses, __init_subclass__,
# or metaclasses at class-creation time
U
// c f runs at compile time
c f validate_schema(schema: S) -> L
    r => schema.len() > 0
03

Classes

Declaration & fields

Python
class Point:
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

    def dist_sq(self) -> float:
        return self.x**2 + self.y**2
U
d Point
    x: N +M
    y: N +M

    f dist_sq() -> N
        r => x*x + y*y
U: no __init__ boilerplate — fields declared directly, compiler generates constructor. No self parameter — methods access fields directly.

Inheritance

Python
class Shape:
    def area(self) -> float:
        return 0.0

class Circle(Shape):
    def __init__(self, r: float):
        self.r = r
    def area(self) -> float:
        return 3.14159 * self.r ** 2
U
d Shape
    f area() -> N
        r => 0.0

d Circle : Shape
    r: N +M
    f area() -> N
        r => 3.14159 * r * r

Dataclasses

Python
from dataclasses import dataclass, field

@dataclass
class Config:
    host: str = "localhost"
    port: int = 5432
    tags: list = field(default_factory=list)
U
d Config
    host: S  +M = "localhost"
    port: I  +M = 5432
    tags: [S] = []  // array is +R+M(MVCC) by default
No decorator needed. Field defaults are declared inline. +R on tags makes it heap-allocated so mutation works correctly. No field(default_factory=list) trap.
04

Magic Methods

Python's dunder methods map directly to U's __method__ protocol (double underscores on both sides).

Python
class Vector:
    def __init__(self, x, y): ...
    def __add__(self, other):
        return Vector(self.x+other.x, self.y+other.y)
    def __str__(self):
        return f"({self.x}, {self.y})"
    def __len__(self): return 2
    def __eq__(self, other): ...
U
d Vector
    x: N +M
    y: N +M
    f __add__(other: Vector) -> Vector
        r => Vector(x+other.x, y+other.y)
    f __string__() -> S
        r => "(" + x + ", " + y + ")"
    f __len__() -> I => 2
PythonUTriggered by
__init__field defaultsConstructor — auto-generated
__str____string__String coercion
__repr____repr__Debug display
__len____len__.len property
__eq____eq__==
__lt__ / __gt____lt__ / __gt__< / >
__add____add__+
__getitem____get__[key]
__setitem____set__[key] = val
__contains____has__x in obj
__hash____hash__Map key / set member
__iter____iter__.x() iteration
__enter__ / __exit____open__ / __close__Resource scope
__call____call__obj(args)
__pack__ / __unpack____pack__ / __unpack__Serialization (replaces pickle protocol)
05

Lists → Arrays

Creation & basic ops

Python
items = [1, 2, 3]
items.append(4)
items.extend([5, 6])
items.pop()        # removes last
items.pop(0)      # removes first
items.insert(1, 99)
items.remove(3)   # remove by value
len(items)
U
items: [I] = [1, 2, 3]  // [I] is +R+M(MVCC) by default
items.append(4)
items.extend([5, 6])
items.pop()        // removes last
items.pop(1)      // removes by index
items.insert(2, 99)
items.delete(3)   // remove by value
items.len          // property, no parens

Slicing

Python
items[1:3]      # indices 1,2
items[:3]       # first 3
items[2:]       # from index 2
items[::2]      # every other
items[::-1]     # reversed
U
items[1..3]     // inclusive [1,2,3]
items[1...3]    // exclusive [1,2]
items.first(3)  // first 3
items.skip(2)   // from index 2
items.step(2)   // every other
items.reverse()  // reversed copy
U ranges are .. (inclusive) and ... (exclusive). Python's 0-based half-open slicing is replaced by named methods for common patterns.

Search & membership

Python
3 in items          # True
items.index(3)     # first position
items.count(3)     # occurrences
any(x > 2 for x in items)
all(x > 0 for x in items)
U
3 in items           // true
items.index(3)      // first position
items.count(3)      // occurrences
items.any(x => x > 2)
items.all(x => x > 0)

Sorting & transformation

Python
items.sort()                    # in-place
items.sort(key=lambda x: -x)  # custom
sorted(items)                   # new list
items.reverse()                 # in-place
list(map(lambda x: x*2, items))
list(filter(lambda x: x>2, items))
U
items.sort()                    // in-place
items.sort(x => -x)             // custom key
items.sorted()                  // new array
items.reverse()                 // in-place
items.x(x => x*2)              // map
items.x(x => x>2 ? x ! none)   // filter
06

Dicts → Maps

Basic operations

Python
d = {"a": 1, "b": 2}
d["c"] = 3
d.get("x", 0)   # 0 if missing
"a" in d
del d["a"]
len(d)
d.keys(); d.values(); d.items()
U
d: {S: I} = {"a": 1, "b": 2}  // {K:V} is +R+M(MVCC) by default
d["c"] = 3
d.get("x", 0)   // 0 if missing
"a" in d
d.delete("a")
d.len
d.keys(); d.values(); d.items()

Defaulting maps (defaultdict)

Python
from collections import defaultdict

groups = defaultdict(list)
groups["eng"].append(user)

# nested — must use lambda:
freq = defaultdict(lambda: defaultdict(int))
freq[doc][word] += 1
U
groups: {S: [User] !}  // +R+M(MVCC) implicit
groups["eng"].append(user)

// nested — ! composes, no lambda trap:
freq: {S: {S: I !} !}
freq[doc][word] += 1
U avoids the Python trap: defaultdict(defaultdict(int)) shares one inner dict across all keys. U's ! always creates a fresh instance per key — structurally impossible to get wrong.

Merging & updating

Python
merged = {**d1, **d2}   # 3.5+
merged = d1 | d2        # 3.9+
d1.update(d2)
U
merged = d1 | d2        // union
d1 << d2               // patch (MVCC-aware)
07

Sets

Python
s = {1, 2, 3}
s.add(4)
s.discard(2)
3 in s
s | t   # union
s & t   # intersection
s - t   # difference
len(s)
U
s: {I} = {1, 2, 3}  // {T} set is +R+M(MVCC) by default
s.insert(4)      // returns L (had effect?)
s.delete(2)      // returns L
3 in s
s | t    // union
s & t    // intersection
s - t    // difference
s.len
{} = empty set. {:} = empty map (the : signals map). insert/delete return L (bool) — true if the operation had an effect.
08

Strings

Python
s = "hello world"
s.upper()           # "HELLO WORLD"
s.lower()           # "hello world"
s.strip()           # trim both ends
s.lstrip()          # trim left
s.rstrip()          # trim right
s.split(" ")        # ["hello","world"]
s.replace("hello","hi")
s.startswith("hel")
s.endswith("rld")
s.find("wor")       # index or -1
" ".join(["a","b"])  # "a b"
len(s)
U
s: S = "hello world"
s.upper()           // "HELLO WORLD"
s.lower()           // "hello world"
s.strip()           // trim both ends
s.lstrip()          // trim left
s.rstrip()          // trim right
s.split(" ")        // ["hello","world"]
s.replace("hello","hi")
s.startswith("hel")
s.endswith("rld")
s.find("wor")       // index or none
["a","b"].join(" ") // "a b"
s.len
String methods are intentionally Python-style. find() returns +N (none instead of -1). .join() is on the array, not the separator string.

Formatting

Python
name, age = "Alice", 30
f"Hello {name}, age {age}"
"Hello {}, age {}".format(name, age)
U
name: S = "Alice"; age: I = 30
"Hello {name}, age {age}"  // interpolation

Template literals (typed, injection-safe)

Python
# no typed template literals
# raw f-strings go to sql/html unsafely:
query = f"SELECT * FROM users WHERE id={uid}"
# ^ SQL injection risk — no compile check
U
// typed — returns SQL, not S
query = sql`SELECT * FROM users WHERE id={uid}`
page  = html`<h1>{title}</h1>`

// passing S where SQL expected → compile error
// {uid} is parameterised, never concatenated
U template tags are injection-proof. The sql`` tag returns a SQL type, not S. The interpolated values are parameterised by the module — never concatenated into the query string.
09

Conditionals

Python
# if/elif/else
if x > 0:
    sign = "positive"
elif x < 0:
    sign = "negative"
else:
    sign = "zero"

# ternary
label = "yes" if flag else "no"
U
// chained ?! (no if/elif/else keywords)
sign = x > 0 ? "positive"
     ! x < 0 ? "negative"
     !          "zero"

// ternary
label = flag ? "yes" ! "no"

Null checks

Python
name = user.name if user else "anon"

# walrus operator
if (n := get_name()) is not None:
    print(n)
U
name = user ?? "anon"     // null coalesce

n = get_name()
n != none & process(n)    // guard: short-circuit
10

Loops & Iteration

Python
for x in items:
    print(x)

for i, x in enumerate(items):
    print(i, x)

for i in range(1, 6):     # 1..5
    ...

while condition:
    ...
U
items.x(x => process(x))

items.x((i, x) => process(i, x))  // with index

[1..5].x(i => ...)     // inclusive range

// while: use recursion or generator
// no while keyword — use .x() or +W
No while keyword — bounded iteration via .x(), unbounded sequences via generators (+W). This makes it impossible to accidentally write an infinite loop without declaring generator semantics.

zip / enumerate equivalents

Python
for a, b in zip(xs, ys):
    ...

for i, x in enumerate(items, start=1):
    ...
U
xs.zip(ys).x((a, b) => ...)

items.x((i, x) => ...)  // 1-based by default
11

Comprehensions

Python
# list comprehension
squares = [x**2 for x in items]

# with filter
evens = [x for x in items if x % 2 == 0]

# dict comprehension
d = {k: v*2 for k, v in pairs}

# set comprehension
s = {x % 10 for x in items}
U
// map via .x()
squares = items.x(x => x*x)

// filter+map (none values dropped)
evens = items.x(x => x % 2 == 0 ? x ! none)

// dict from pairs
d = pairs.x((k,v) => {k: v*2}).merge()

// set
s = items.x(x => x % 10).toSet()
12

Lambdas & Closures

Python
# lambda (expression only)
double = lambda x: x * 2

# multi-param
add = lambda a, b: a + b

# closure over outer variable
def make_adder(n):
    return lambda x: x + n

plus5 = make_adder(5)
plus5(10)  # 15
U
// => arrow (expression or block)
double: I+F = x => x * 2

// multi-param
add: I+F = (a, b) => a + b

// closure (captures by reference)
f make_adder(n: I) -> (I)->I
    r => x => x + n

plus5 = make_adder(5)
plus5(10)  // 15
13

Error Handling

Python
class NetworkError(Exception): ...

def fetch(url: str) -> str:
    try:
        return http.get(url)
    except NetworkError as err:
        log(err)
        raise
    finally:
        cleanup()
U
d NetworkError : Error
    msg: S

f fetch(url: S) -> S ! NetworkError
    result = http.get(url) ! err =>
        log(err)
        e NetworkError(err.msg)
    r => result
Declared errors: ! NetworkError in the return type is a compile-time declaration. Callers know what can fail. No more undocumented exceptions surfacing at runtime.
14

Generators

Python
def count_up(n: int):
    i = 0
    while i < n:
        yield i
        i += 1

for x in count_up(5):
    print(x)
U
f count_up(n: I) -> I +W
    [0..n].x(i =>
        w i)

count_up(5).x(x => process(x))
+W on the return type declares generator semantics. w (wield) is the yield keyword. The compiler enforces that w only appears inside +W functions.
15

Async & Concurrency

Basic async function

Python
import asyncio

async def fetch(url: str) -> str:
    return await http.get(url)

async def main():
    result = await fetch("https://...")

# must be called with asyncio.run()
asyncio.run(main())
U
a f fetch(url: S) -> S
    r => a http.get(url)

// call with a prefix or block inline
result = a fetch("https://...")
// or: result: S+A = a fetch(...)  ← pending type

Parallel execution

Python
import asyncio

# gather runs coroutines in parallel
results = await asyncio.gather(
    fetch(url1), fetch(url2), fetch(url3)
)
U
// +A values are pending — run in parallel
pending: [S +A] = [
    a fetch(url1),
    a fetch(url2),
    a fetch(url3)
]
results: [S] = pending.all()
No function coloring. Python splits the world into sync and async — you can't await from a sync function. In U, a fetch() works from any context; the +A type propagates, but never infects callers.

Shared mutable state

Python
import asyncio
cache = {}
lock = asyncio.Lock()

async def update(key, val):
    async with lock:
        cache[key] = val  # manual lock
U
cache: {S: S} = {:}  // +M(MVCC) is already the default

a f update(key: S, val: S) -> none
    cache[key] = val  // MVCC handles concurrency
No manual locks. +M(MVCC) declares the write policy. The compiler rejects concurrent writes without a declared policy. No lock = no data race, not "probably fine."
16

Modules & Imports

Python
import json
from pathlib import Path
from typing import Optional

# relative import
from .utils import helper
U
o Json
o Fs { Path }
o Network { HTTP, WebSocket }

// capability grant at import time
o Database { Query } // grants DB capability
Capability system: o (outside) imports declare capabilities. A module that doesn't import Network cannot make network calls — the compiler enforces it. No accidental transitive dependencies.
17

Null Safety

Python
# None can appear anywhere
def get_user(id: int) -> Optional[User]:
    ...

user = get_user(42)
user.name  # AttributeError if None — runtime!

# must check manually
if user is not None:
    print(user.name)
U
// +N marks nullable — -N is default
f get_user(id: I) -> User +N
    ...

user = get_user(42)
user.name  // compile error: user is +N

// must narrow first
user != none & process(user.name)
// or: user?.name  (optional chain)
// or: user ?? default_user
Null errors are compile errors in U. Python's Optional[T] is documentation; U's +N is enforced. After a != none check, the compiler narrows the type automatically.
18

Type System

Python
# structural (duck typing)
def process(x):  # any type
    x.quack()   # fails at runtime if no .quack

# type hints (not enforced)
def add(a: int, b: int) -> int: ...

# Protocol for structural typing
from typing import Protocol
class Drawable(Protocol):
    def draw(self): ...
U
// nominal + structural, fully enforced
f process(x: Drawable) -> none
    x.draw()  // compile error if Drawable not met

// interface declaration
d Drawable
    f draw() -> none

// no any, no object, no escape hatch
U has no any, no object, no typing.cast. Every value has a statically known type at all times. Structural typing via interface declarations, nominal typing for class hierarchies.
19

Memory Model

Python
# everything on heap, GC manages
x = 5          # heap int object
items = []     # heap list
# no control over allocation
# GC pauses unpredictable
import sys
sys.getrefcount(x)  # manual inspection only
U
// -R (default): stack, no alloc cost
x: I = 5

// +R: heap, ARC (no GC pauses)
config: Config +R = Config()

// +R -M: shared immutable (free to alias)
data: Data +R -M = load()

// explicit copy when needed
local = heap_val.c()
ARC not GC. U uses automatic reference counting — deterministic cleanup, no GC pauses. You control stack vs heap with -R/+R. Default is stack (-R) — zero allocation cost.
20

Access Control

Python
class Foo:
    def public_method(self): ...
    def _private(self): ...    # convention only
    def __mangled(self): ...   # name-mangled

# nothing is truly enforced at runtime
obj._private()  # works fine — just a warning
U
d Foo
    f public_method() -> none ...
    f _private() -> none ...   // enforced

// outside callers get compile error:
foo._private()  // ERROR: _private is module-private

// __method__ = magic/protocol methods only
U enforces _prefix at compile time. Python's underscore is convention; U's is a hard rule. __name__ (double both sides) is reserved for magic methods only — no name mangling.
21

Empty Collection Footguns

Python uses {} for an empty dict and set() for an empty set — asymmetric and a common source of bugs. U uses syntax that signals the structure directly.

Python
# {} is empty DICT, not set
d = {}         # dict — OK
s = set()      # set — need set() constructor
s = {}         # WRONG — this is a dict!

# checking which one you have:
type({})       # <class 'dict'>
type(set())   # <class 'set'>

# non-empty: same literal syntax
d = {"a": 1}   # dict ← colon signals map
s = {1, 2}    # set
U
// {} = empty SET (consistent with {1, 2})
s = {}         // set — unambiguous

// {:} = empty MAP (: always signals map)
d = {:}      // map — unambiguous

// checking: the type is in the declaration
s: {I}         // set of I
d: {S: I}     // map S→I

// non-empty: same rule
d = {"a": 1}   // map ← colon signals map
s = {1, 2}    // set
Python footgun: {} is dict, set() is set. Asymmetric because dict came first. U uses : as the consistent discriminator — if you see :, it's a map; if you don't, it's a set.
22

.join() and .split()

U supports both Python-style (sep.join(array)) and JS-style (array.join(sep)) — whichever feels natural.

Python
# join: sep.join(iterable)
" ".join(["a", "b", "c"])   # "a b c"

# split: str.split(sep)
"a b c".split(" ")         # ["a","b","c"]
"a b c".split()            # split on whitespace
U — both styles work
// JS-style (array method)
["a", "b", "c"].join(" ")  // "a b c"

// Python-style (string method) — also valid
" ".join(["a", "b", "c"]) // "a b c"

// split — string method, same as Python
"a b c".split(" ")         // ["a","b","c"]
"a b c".split()            // split on whitespace
Both array.join(sep) and sep.join(array) are valid — U accepts either. .split() is on the string in both Python and U, so no migration needed there.
23

Multi-line Expressions

Both Python and U use parentheses for line continuation — same muscle memory.

Python
# ( continues across lines
result = (
    first_value
    + second_value
    + third_value
)

query = (
    "SELECT * FROM users "
    "WHERE active = true "
    "ORDER BY name"
)
U
// ( continues across lines — same rule
result = (
    first_value
    + second_value
    + third_value
)

query = sql`
    SELECT * FROM users
    WHERE active = true
    ORDER BY name
`(args)
Python and U share the same line-continuation rule: open paren continues until close paren. In U, multi-line template literals also work naturally — the backtick stays open across lines.

Quick Reference

ConceptPythonU
Functiondef f(x: int) -> int:f name(x: I) -> I
Returnreturn xr => x
Classclass Foo:d Foo
Inheritclass B(A):d B : A
Async fnasync def f():a f name()
Awaitawait f()a f()
Yieldyield xw x
Importfrom m import Xo Module { X }
Throwraise NetworkError()e NetworkError()
Ternarya if cond else bcond ? a ! b
Null coalescex if x is not None else yx ?? y
Optional chainx.foo if x else Nonex?.foo
List append.append(x).append(x)
List extend.extend(xs).extend(xs)
Membershipx in xsx in xs
Any / allany(f(x) for x in xs)xs.any(f) / xs.all(f)
Dict items.items().items()
String lower.lower().lower()
String strip.strip().strip()
Mutabledefault (all)scalars: -M default · collections: +M(MVCC) default
Heap allocdefaultopt-in (+R)
NullableOptional[T] (unenforced)T+N (compile-enforced)
Private_name (convention)_name (enforced)
Range incl.range(1, 6)[1..5]
Range excl.range(1, 5)[1...5]
Defaultdictdefaultdict(list){K: [V]+R !} +M
f-stringf"Hello {name}""Hello {name}"
SQL (safe)f"SELECT ... {val}" ⚠sql`SELECT ... {val}` ✓