Notes from a Q&A session (October 2026). Companion to KOTLIN.md.
Python is a dynamically typed, interpreted, general-purpose language created by Guido van Rossum. First release 1991; Python 3 in 2008; Python 2 reached end of life in 2020. Its main design goal is readability and developer productivity: "there should be one obvious way to do it."
Governance: the Python Software Foundation owns the trademark; an elected Steering Council (since 2019, after Guido stepped down as "BDFL") decides language changes via PEPs (Python Enhancement Proposals). A new version is released every October: 3.14 in October 2025, 3.15 due October 2026.
CPython is the reference implementation: an interpreter written in C, with the standard library largely in Python. A few million lines, roughly half Python, half C.
| Directory | What it is |
|---|---|
Programs/ |
The main() of the python executable (python.c) |
Modules/ |
Startup code (main.c) and stdlib modules written in C (_io, _json, _asyncio, …) |
Parser/ |
Tokenizer and PEG parser; grammar in Grammar/python.gram, parser code generated from it |
Python/ |
The core: compiler (compile.c, symtable.c, flowgraph.c), interpreter loop (ceval.c, bytecodes.c), startup/shutdown (pylifecycle.c), imports, GIL, JIT |
Objects/ |
Built-in types: int, str, list, dict, functions, classes (longobject.c, dictobject.c, typeobject.c, …) |
Include/ |
C headers, including the C API for extension modules |
Lib/ |
The standard library in Python (asyncio, json, pathlib, unittest, …) |
Lib/test/ |
The very large test suite |
Doc/ |
Official documentation (reStructuredText) |
InternalDocs/ |
Developer docs on interpreter internals; a good starting point |
PC/, Mac/, Tools/, Misc/ |
Platform code, build tools, changelog |
Other implementations: PyPy (JIT, much faster for plain Python), MicroPython (microcontrollers), GraalPy (JVM via GraalVM), and the largely inactive Jython and IronPython.
Programs/python.c → Py_BytesMain() in Modules/main.c → pymain_run_python(). Initialization in Python/pylifecycle.c (Py_InitializeFromConfig).source → tokenizer → PEG parser → AST → compiler (symtable, CFG optimizer)
→ bytecode (.pyc cached in __pycache__) → evaluation loop (ceval.c)
→ specializing adaptive interpreter (3.11+) → experimental JIT (3.13+)
Python/bytecodes.c (generates generated_cases.c.h); Python/ceval.c runs them.Include/object.h (PyObject, reference counting), Objects/typeobject.c (classes, attribute lookup).Python/ceval_gil.c, plus the free-threaded build (--disable-gil).InternalDocs/ and the devguide (devguide.python.org).| Kotlin | Python | |
|---|---|---|
| Created by | JetBrains (2011) | Guido van Rossum (1991) |
| Typing | Static | Dynamic (optional type hints) |
| Runs on | JVM, native, JS, Wasm | CPython interpreter (+ PyPy, etc.) |
| Compiler/interpreter written in | Kotlin | C (+ Python for the stdlib) |
| Main market | Android, JVM backends | AI/ML, data, scripting, web |
| Governance | JetBrains + Kotlin Foundation | PSF + Steering Council |
| IDE | Notes |
|---|---|
| VS Code + Python/Pylance extensions | Most popular. Free, good completion, debugging, Jupyter support |
| PyCharm (JetBrains) | Most complete Python IDE: strongest refactoring, Django and database tools. Free community version + paid Professional |
| Jupyter / JupyterLab | Notebooks: code, output and charts together |
| Spyder | MATLAB-like data-science IDE, bundled with Anaconda |
| Cursor, Zed, Vim/Neovim | Good support via language servers |
pyproject.toml: standard project config, roughly like build.gradle.kts or pom.xml.pytest (standard), Hypothesis (property-based), unittest.mock, tox / nox (multiple Python versions).
For beginners: install uv + VS Code (or PyCharm), uv init, uv add <package>, add Ruff.
No single dominant UI framework; UI is one of Python's weaker areas.
| Framework | Use |
|---|---|
| Tkinter | In the stdlib; simple, dated look |
| PySide6 / PyQt6 | Qt bindings; most capable desktop option. PySide LGPL, PyQt GPL/commercial |
| Kivy | Touch/mobile apps, custom look |
| BeeWare (Toga) | Native widgets on desktop, iOS, Android |
| Flet | Flutter-based UIs in Python |
| Dear PyGui | Fast GPU-rendered tool UIs |
| Streamlit, Gradio | Very popular for data/AI apps: script → web UI in a few lines |
| Dash, Panel | Data dashboards |
| NiceGUI, Reflex | Web UIs written entirely in Python |
| PyScript / Pyodide | Python in the browser via WebAssembly |
import streamlit as st
name = st.text_input("Name")
if st.button("Greet"):
st.write(f"Hello {name}")
No Jetpack Compose equivalent for mainstream mobile. In practice: Qt desktop apps or web UIs for data/AI.
No compile step: source code is deployed and CPython runs it.
Python apps don't embed an HTTP server like a Spring Boot fat JAR; a separate application server loads the app via a standard interface:
| Standard | Type | Servers | Frameworks |
|---|---|---|---|
| WSGI | Synchronous | Gunicorn, uWSGI | Django, Flask |
| ASGI | Async (+ WebSockets) | Uvicorn, Hypercorn, Granian | FastAPI, Starlette, Django (async) |
gunicorn myapp.wsgi -w 4
uvicorn myapp.main:app --host 0.0.0.0 --port 8000
Because of the GIL, servers usually run one worker process per CPU core (Java: one process, many threads).
FROM python:3.13-slim
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev
COPY . .
EXPOSE 8000
CMD ["uv", "run", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
| Option | Notes |
|---|---|
| Docker on Kubernetes / cloud containers | Most common |
| Serverless (Lambda, Cloud Functions) | Very common, good fit: fast startup |
| PaaS (Heroku, Render, Fly.io, App Engine) | Popular for small apps |
| VM + systemd + nginx | Classic setup |
Pain points: native dependencies (wheels must match OS/CPU) and image size (PyTorch + CUDA images can be several GB).
DI containers are uncommon. Anything can be replaced at runtime (monkeypatching), so tests don't need DI. Dependencies are usually passed explicitly or kept as module-level objects.
Depends:def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/users/{id}")
def read_user(id: int, db: Session = Depends(get_db)):
return db.get(User, id)
| Area | Django | FastAPI | Flask |
|---|---|---|---|
| Philosophy | Batteries included (like Spring Boot) | Modern, async, type-hint-driven | Minimal (like Ktor) |
| HTTP server | WSGI/ASGI | ASGI (Uvicorn) | WSGI (Gunicorn) |
| REST | DRF / Django Ninja | Built in, auto OpenAPI/Swagger | Extensions |
| JSON / validation | DRF serializers | Pydantic | Your choice |
| Async | Partial | Native | Limited |
| Database | Django ORM + migrations | SQLAlchemy, SQLModel | SQLAlchemy |
| Admin UI | Built in | No | No |
| Auth | Built in | OAuth2/JWT helpers | Extensions |
| Templates | Django templates | Optional Jinja2 | Jinja2 |
| Streaming | Channels (WebSockets) | WebSockets, SSE, streaming responses | Limited |
Common standalone libraries: httpx / requests, SQLAlchemy, Pydantic, Celery / RQ / Dramatiq (background jobs), orjson, structlog.
A very large standard library: about 200+ modules (unlike Kotlin's deliberately small one).
| Area | Modules |
|---|---|
| Data structures | collections (Counter, defaultdict, deque), heapq, bisect, array, dataclasses, enum |
| Text | re, string, textwrap, difflib, unicodedata |
| Files and OS | pathlib, os, shutil, glob, tempfile, subprocess |
| Data formats | json, csv, tomllib, xml, sqlite3, pickle, zipfile, gzip |
| Networking / web | urllib, http.client, http.server, socket, ssl, email, smtplib |
| Concurrency | threading, multiprocessing, concurrent.futures, asyncio, queue |
| Functional tools | itertools, functools, operator |
| Date and time | datetime, zoneinfo, time, calendar |
| Math | math, statistics, decimal, fractions, random |
| Developer tools | unittest, doctest, logging, argparse, pdb, cProfile, typing |
| Security | hashlib, hmac, secrets |
Downside: some stdlib modules are dated; the community often prefers third-party alternatives (requests/httpx over urllib).
Python uses comprehensions, generators and built-in functions instead of method chains.
// Java
List<String> names = users.stream()
.filter(u -> u.getAge() > 18)
.map(User::getName)
.sorted()
.collect(Collectors.toList());
names = sorted(u.name for u in users if u.age > 18)
| Syntax | Result | Evaluation |
|---|---|---|
[x * 2 for x in xs] |
list | Eager |
{x for x in xs} / {k: v for ...} |
set / dict | Eager |
(x * 2 for x in xs) |
generator | Lazy (like Streams / Sequences) |
map(f, xs), filter(p, xs) |
iterator | Lazy |
first_adult = next(u for u in users if u.age > 18)
def powers_of_two():
n = 1
while True:
yield n
n *= 2
from itertools import islice
list(islice(powers_of_two(), 10)) # [1, 2, 4, ..., 512]
sum, min, max, any, all, sorted(key=...), zip, enumerate, reversed, lenitertools: chain, islice, takewhile, dropwhile, groupby (adjacent items only; sort first), batched (3.12+), pairwise, accumulate, product, permutationsfunctools: reduce, partialcollections: Counter, defaultdictfrom collections import Counter, defaultdict
by_city = defaultdict(list)
for u in users:
by_city[u.city].append(u) # groupBy
adults = [u for u in users if u.age >= 18] # partition
minors = [u for u in users if u.age < 18]
total = sum(o.price for o in orders) # sumOf
counts = Counter(u.city for u in users)
oldest = max(users, key=lambda u: u.age) # maxBy
concurrent.futures or multiprocessing.df.filter(pl.col("age") > 18).group_by("city").agg(pl.len())
async for with async generators (rough counterpart to Kotlin's Flow).Generics exist only in type hints: checked by static tools (mypy, pyright), ignored at runtime.
names: list[str] = ["Ann", "Bob"]
names.append(42) # runs fine; only the type checker complains
class Box[T]:
def __init__(self, value: T) -> None:
self.value = value
def first_or_default[T](items: list[T], default: T) -> T:
return items[0] if items else default
def max_of[T: (int, float, str)](a: T, b: T) -> T: ... # constrained
def biggest[T: Comparable](items: list[T]) -> T: ... # upper bound (a Protocol you define)
type Pair[A, B] = tuple[A, B]
Older code: T = TypeVar("T"), class Box(Generic[T]).
| Feature | Kotlin | Python |
|---|---|---|
| Enforcement | Compiler | Optional static checkers; nothing at runtime |
| Read-only vs. mutable | List / MutableList |
list vs. read-only Sequence/Mapping types; tuple/frozenset truly immutable |
| Nullability | String? |
str \| None |
| Variance | out / in |
Inferred (3.12+) or TypeVar(covariant=True) |
| Structural typing | No | Protocol (like Go interfaces) |
| Runtime type info | Erased (except reified) |
Available via typing.get_type_hints(); Pydantic validates with it |
from collections.abc import Sequence
def total(xs: Sequence[float]) -> float: # read-only, covariant
return sum(xs)
Lambdas are limited to a single expression; for more, use a nested def (functions are first-class objects).
square = lambda x: x * x
add = lambda a, b: a + b
greet = lambda: print("hi")
| Java | Kotlin | Python |
|---|---|---|
x -> x * x |
{ x -> x * x } |
lambda x: x * x |
Function<T, R> |
(T) -> R |
Callable[[T], R] |
| Multi-statement | Multi-statement | No: use def |
| Effectively-final captures | Modifiable captures | Modifiable with nonlocal |
String::toUpperCase |
String::uppercase |
str.upper |
users.sort(key=lambda u: u.age)
list(map(str.upper, names))
def make_counter():
count = 0
def inc():
nonlocal count
count += 1
return count
return inc
from functools import partial
from operator import attrgetter
base2 = partial(int, base=2)
users.sort(key=attrgetter("age"))
fs = [lambda: i for i in range(3)]
[f() for f in fs] # [2, 2, 2]
fs = [lambda i=i: i for i in range(3)] # fix: default argument
Python style prefers comprehensions over map/filter with lambdas.
Multi-paradigm but leans imperative and object-oriented. Guido van Rossum was skeptical of heavy FP (reduce moved to functools, lambdas stayed one expression).
def compose(f, g):
return lambda x: f(g(x))
from dataclasses import dataclass, replace
@dataclass(frozen=True)
class User:
name: str
age: int
older = replace(user, age=user.age + 1) # like Kotlin's copy()
# Structural pattern matching (3.10+): more powerful than Kotlin's "when"
match shape:
case Circle(r=r):
area = 3.14 * r * r
case Rect(w=w, h=h) if w == h:
area = w * w
case Rect(w=w, h=h):
area = w * h
case {"type": "point", "coords": [x, y]}:
area = 0
type Shape = Circle | Rect # sum type; exhaustiveness via type checker + assert_never
Also: generators, comprehensions, functools (reduce, partial, lru_cache/cache, singledispatch), itertools, decorators.
| Missing | Notes |
|---|---|
| Immutability by default | No val; lists and dicts mutable |
| Multi-statement lambdas | Use def |
| Tail-call optimization | Recursion limit ~1000 |
| Persistent collections | Only via pyrsistent |
| Everything as expression | if/try are statements (ternary a if c else b exists) |
| Enforced type safety | Optional only |
| Higher-kinded types, type classes | No (Protocol partially) |
| Mutable default args gotcha | def f(xs=[]) shares one list |
Libraries: toolz, more-itertools, returns (Result/Maybe), pyrsistent.
| Kotlin | Python | |
|---|---|---|
| Pattern matching | Limited when |
Full structural matching |
| Sealed types + exhaustiveness | Compiler | Type checker (optional) |
| Immutability | Default | Opt-in |
| Lambdas | Full blocks | One expression |
| Expressions | Almost everything | Statement-oriented |
| Lazy sequences | Sequence |
Generators (yield) |
No checked exceptions (like Kotlin), and exceptions are used more freely than in Java, including for control flow.
try:
value = int(text)
except ValueError as e:
value = 0
except (TypeError, KeyError): # multi-catch
raise
else:
print("only if no exception")
finally:
cleanup()
"Easier to Ask Forgiveness than Permission" vs. LBYL ("look before you leap"):
try:
value = config["timeout"]
except KeyError:
value = 30
value = config.get("timeout", 30)
Iterators end via StopIteration.
with open("data.txt") as f: # like try-with-resources / use {}
content = f.read()
from contextlib import contextmanager
@contextmanager
def timer():
start = time.time()
yield
print(time.time() - start)
try:
load()
except IOError as e:
raise ConfigError("cannot load config") from e
try: # exception groups (3.11+)
async with asyncio.TaskGroup() as tg:
tg.create_task(a())
tg.create_task(b())
except* ValueError as eg:
...
e.add_note("while processing user 42") # 3.11+
class InsufficientFunds(Exception):
def __init__(self, needed: float):
super().__init__(f"needs {needed} more")
self.needed = needed
Errors as values are less common than in Kotlin (None, tuples, or the returns library).
| Java | Kotlin | Python | |
|---|---|---|---|
| Checked exceptions | Yes | No | No |
| Multi-catch | Yes | No | Yes |
| Resource cleanup | try-with-resources | .use {} |
with |
else block |
No | No | Yes |
| Exception groups | No | No | Yes (except*) |
| Typical style | Exceptions for errors | Exceptions for bugs, values for expected failures | EAFP |
Central topic: the GIL — in standard CPython only one thread runs Python code at a time.
| Tool | Good for | Multiple cores? |
|---|---|---|
threading |
I/O-bound work (GIL released while waiting) | No, not for Python code |
multiprocessing |
CPU-bound work; separate processes | Yes; more memory, data serialized |
concurrent.futures |
Thread/process pools | Depends on pool |
asyncio |
Many concurrent I/O operations | No (single thread) |
| Free-threaded Python (3.13+, supported in 3.14) | True multithreading | Yes, if libraries support it |
Subinterpreters (concurrent.interpreters, 3.14) |
Isolated interpreters, own GIL each | Yes |
| C extensions (NumPy, PyTorch) | Release the GIL internally | Yes |
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
with ThreadPoolExecutor(max_workers=10) as pool: # I/O-bound
pages = list(pool.map(fetch, urls))
with ProcessPoolExecutor() as pool: # CPU-bound
results = list(pool.map(heavy_math, data))
import asyncio
async def load_user(id: int) -> User:
profile = await api.fetch_profile(id)
orders = await api.fetch_orders(id)
return User(profile, orders)
async def dashboard():
async with asyncio.TaskGroup() as tg: # structured concurrency (3.11+)
user = tg.create_task(api.fetch_user())
news = tg.create_task(api.fetch_news())
return Dashboard(user.result(), news.result())
asyncio.run(dashboard())
| Kotlin coroutines | Python asyncio | |
|---|---|---|
| Keywords | suspend |
async / await |
| Structured concurrency | Default (scopes) | TaskGroup; stricter in Trio / AnyIO |
| Threads | Multi-threaded dispatchers | Single-threaded event loop |
| Blocking calls | Dispatchers.IO |
Freeze the loop; use asyncio.to_thread() |
| Cancellation | CancellationException |
CancelledError |
| Streams | Flow, Channel |
Async generators, asyncio.Queue |
| Spreads to callers | Yes | Yes ("function coloring") |
Synchronization: threading.Lock, RLock, Semaphore, Event, Condition, queue.Queue, plus asyncio versions. The GIL doesn't make code thread-safe: counter += 1 can still race. Free-threaded builds make locks matter even more.
The two languages target different problems, so a detailed head-to-head is of limited use. In short:
| Python | Java | |
|---|---|---|
| Typing | Dynamic (optional hints) | Static, enforced |
| Strengths | AI/ML, data, scripting, prototyping, automation, learning | Large long-lived backends, high throughput, enterprise, big data infrastructure, Android |
| Productivity | Very fast to write; less ceremony | More verbose; safer large-scale refactoring |
| Tooling | VS Code/PyCharm, uv, Ruff, pytest | IntelliJ, Maven/Gradle, JUnit |
| Plain CPU-bound speed | 10–100× slower (CPython) | Fast (JIT) |
| Numeric/ML speed | Fast via C/C++/CUDA libraries | Weak ecosystem |
| Parallelism | GIL; processes or free-threaded builds | True multithreading, virtual threads |
| Memory | Low baseline, heavy per object | Higher baseline, compact objects |
| Testing | Easy to write and mock, but more tests needed | Compiler catches more before tests run |
Typical real-world split: Python for ML, data and scripting; Java/Kotlin for core services; connected via APIs or message queues.