Python → U — 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
| Python | U | Triggered by |
|---|---|---|
| __init__ | field defaults | Constructor — 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] += 1U 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 default11
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 concurrencyNo 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
| Concept | Python | U |
|---|---|---|
| Function | def f(x: int) -> int: | f name(x: I) -> I |
| Return | return x | r => x |
| Class | class Foo: | d Foo |
| Inherit | class B(A): | d B : A |
| Async fn | async def f(): | a f name() |
| Await | await f() | a f() |
| Yield | yield x | w x |
| Import | from m import X | o Module { X } |
| Throw | raise NetworkError() | e NetworkError() |
| Ternary | a if cond else b | cond ? a ! b |
| Null coalesce | x if x is not None else y | x ?? y |
| Optional chain | x.foo if x else None | x?.foo |
| List append | .append(x) | .append(x) |
| List extend | .extend(xs) | .extend(xs) |
| Membership | x in xs | x in xs |
| Any / all | any(f(x) for x in xs) | xs.any(f) / xs.all(f) |
| Dict items | .items() | .items() |
| String lower | .lower() | .lower() |
| String strip | .strip() | .strip() |
| Mutable | default (all) | scalars: -M default · collections: +M(MVCC) default |
| Heap alloc | default | opt-in (+R) |
| Nullable | Optional[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] |
| Defaultdict | defaultdict(list) | {K: [V]+R !} +M |
| f-string | f"Hello {name}" | "Hello {name}" |
| SQL (safe) | f"SELECT ... {val}" ⚠ | sql`SELECT ... {val}` ✓ |