Exploring Python: Language, Control Flow, Data Structures, and More
Table of Contents
Language
Control Flow
# Conditionals x = 10 if x > 0: print("positive") elif x < 0: print("negative") else: print("zero") # Match statement (Python 3.10+) match command: case "start": start_server() case "stop": stop_server() case _: print("Unknown command") # Loops for i in range(5): print(i) while condition: do_something() if should_stop: break
Data Structures
# Lists numbers = [1, 2, 3, 4, 5] numbers.append(6) squares = [x**2 for x in numbers] # Dictionaries person = {"name": "Alice", "age": 30} person["email"] = "alice@example.com" names = {p["id"]: p["name"] for p in people} # Sets unique = {1, 2, 3, 2, 1} # {1, 2, 3} a = {1, 2, 3} b = {2, 3, 4} a & b # intersection: {2, 3} a | b # union: {1, 2, 3, 4} # Tuples (immutable) point = (3, 4) x, y = point # unpacking
Error Handling
try: result = risky_operation() except ValueError as e: print(f"Value error: {e}") except (TypeError, KeyError) as e: print(f"Error: {e}") else: print("Success!") finally: cleanup() # Context managers with open("file.txt") as f: content = f.read() # Custom exceptions class ValidationError(Exception): pass
Classes
from dataclasses import dataclass from typing import Optional # Traditional class class Person: def __init__(self, name: str, age: int): self.name = name self.age = age def greet(self) -> str: return f"Hello, I'm {self.name}" # Dataclass (Python 3.7+) @dataclass class User: name: str email: str age: Optional[int] = None def is_adult(self) -> bool: return self.age is not None and self.age >= 18
Packaging
# pyproject.toml (modern Python packaging) [project] name = "mypackage" version = "0.1.0" dependencies = [ "requests>=2.28", "pydantic>=2.0", ] [project.optional-dependencies] dev = ["pytest", "ruff", "mypy"] [build-system] requires = ["hatchling"] build-backend = "hatchling.build"
Development
Environment
# Using uv (fast Python package installer) uv venv source .venv/bin/activate uv pip install -e ".[dev]" # Or with pip python -m venv .venv pip install -e ".[dev]"
Testing
# pytest example import pytest def test_addition(): assert 1 + 1 == 2 @pytest.fixture def sample_data(): return {"key": "value"} def test_with_fixture(sample_data): assert sample_data["key"] == "value"
2026 Review
Python in 2026 is a different language from the Python of 2020. Performance improvements, a rethought type system, a transformed tooling ecosystem, and a new role as the lingua franca of the agent era have all compounded. This section documents what changed, what matured, and where the language is heading.
Python 3.13 (October 2024): Free-Threading and JIT
Python 3.13 shipped two headline features, both experimental but production-relevant:
Free-Threaded Mode (PEP 703)
CPython 3.13 introduced a build option (--disable-gil) that removes the Global Interpreter Lock. Python threads can now run truly in parallel on multi-core hardware. The experimental flag PYTHON_GIL=0 enables this at runtime without recompiling.
""" Demonstrate free-threaded Python 3.13+ with concurrent CPU work. Run with: PYTHON_GIL=0 python examples/free_threading.py Requires: Python 3.13+ built with --disable-gil (or 3.13t build) """ import sys import threading import time def cpu_bound_task(n: int, results: list[int], index: int) -> None: """Compute sum of squares — pure CPU work.""" total = sum(i * i for i in range(n)) results[index] = total def run_threaded(num_threads: int = 4, work_size: int = 1_000_000) -> float: results: list[int] = [0] * num_threads threads = [ threading.Thread(target=cpu_bound_task, args=(work_size, results, i)) for i in range(num_threads) ] start = time.perf_counter() for t in threads: t.start() for t in threads: t.join() elapsed = time.perf_counter() - start return elapsed if __name__ == "__main__": gil_status = "disabled" if not sys._is_gil_enabled() else "enabled" # type: ignore[attr-defined] print(f"GIL: {gil_status}") elapsed = run_threaded() print(f"4 threads, 1M work each: {elapsed:.3f}s")
JIT Compiler (PEP 744)
Python 3.13 also shipped a copy-and-patch JIT compiler. It is off by default (PYTHON_JIT=1 to enable) and targets hot inner loops. The specialising adaptive interpreter introduced in 3.11 underpins it.
Python 3.14 (October 2025): Template Strings and Deferred Annotations
Template Strings (PEP 750)
PEP 750 extends f-string syntax with t"..." template literals. Unlike f-strings, template strings return a Template object whose interpolated parts can be inspected before rendering. This makes them safe for SQL, HTML, and shell escaping.
""" Template strings (PEP 750) — Python 3.14+. t-strings return a Template object; the caller controls rendering, enabling safe interpolation into SQL, HTML, or shell commands. """ import html as html_mod # Standard library html module gains t-string support in 3.14 # This example shows the core pattern without stdlib t-string types. def render_html(template) -> str: """Escape interpolated values for safe HTML output.""" parts: list[str] = [] for item in template: if isinstance(item, str): parts.append(item) else: # item.value is the runtime expression result parts.append(html_mod.escape(str(item.value))) return "".join(parts) user_input = "<script>alert('xss')</script>" # In Python 3.14: template = t"<p>Hello, {user_input}!</p>" # safe_html = render_html(template) # => "<p>Hello, <script>alert('xss')</script>!</p>" print("Template strings enable safe interpolation — escaping is caller-controlled.")
Deferred Evaluation of Annotations (PEP 649)
PEP 649 replaces PEP 563 (from __future__ import annotations). Annotations are stored as lazy descriptors evaluated only when accessed via __annotations__. This eliminates forward-reference errors without the string-quoting workaround and is faster than eager evaluation.
""" Deferred annotation evaluation (PEP 649) — Python 3.14+. Annotations are not evaluated at class/function definition time; they are computed lazily. Forward references work without quotes. """ from __future__ import annotations # backport shim; native in 3.14 from dataclasses import dataclass @dataclass class Node: value: int # Forward reference — works without quotes under PEP 649 children: list[Node] def depth(self) -> int: if not self.children: return 0 return 1 + max(child.depth() for child in self.children) root = Node(value=1, children=[ Node(value=2, children=[]), Node(value=3, children=[Node(value=4, children=[])]), ]) print(f"Tree depth: {root.depth()}") # => 2
Python 3.15 Alpha (2026 Preview)
Python 3.15 entered alpha in early 2026. Key proposals under discussion:
- PEP 768 — Inline comprehension scoping cleanup: comprehension variables no longer leak in edge cases
- PEP 779 —
asyncio.TaskGroupimprovements for structured concurrency, aligning with Trio semantics - Immortal objects (PEP 683 follow-on) — extended to more built-in types, reducing reference-count churn
sys.monitoringstabilisation — the low-overhead tracing API (3.12) reaches stable ABI in 3.15
Typing Ecosystem Matured
The typing story that began with PEP 484 (3.5, 2015) reached a stable, practical equilibrium in 2025–2026.
| Feature | PEP | Since | Status |
|---|---|---|---|
TypedDict |
589 | 3.8 | Stable; NotRequired, ReadOnly added |
ParamSpec |
612 | 3.10 | Widely adopted for decorator typing |
TypeVarTuple |
646 | 3.11 | Variadic generics for *args typing |
TypeVar defaults |
696 | 3.13 | Simplifies generic class defaults |
@override |
698 | 3.12 | Explicit subclass method override marker |
type statement |
695 | 3.12 | type Vector = list[float] replaces TypeAlias |
Static analysis is now split between two tools: mypy (pioneer, broad ecosystem support) and pyright (faster, strict, powers Pylance in VS Code). Most teams run both in CI.
Tooling: uv and ruff Replace the Stack
Astral shipped uv (Rust-based Python installer and resolver) and ruff (Rust-based linter and formatter) in 2023–2024. By 2026 they have effectively replaced the previous standard stack:
| Old tool | Replacement | Speed gain |
|---|---|---|
pip + pip-tools |
uv pip / uv sync |
10--100x |
poetry |
uv (project management) |
10x |
virtualenv |
uv venv |
80x |
flake8 |
ruff check |
100x |
black |
ruff format |
30x |
isort |
ruff check --select I |
included |
uv reads the same pyproject.toml as poetry; migration is largely mechanical. The lockfile (uv.lock) is cross-platform and reproducible.
Python in the Agent Era
Python became the default language for AI agent development in 2024–2026, driven by three converging forces: the Anthropic and OpenAI SDKs shipping Pythonic APIs first, the MCP (Model Context Protocol) SDK targeting Python as its reference implementation, and the pydantic library becoming the canonical interface for structured LLM output.
MCP SDK
The MCP Python SDK provides the reference server and client implementations. An MCP server exposes tools, resources, and prompts over a JSON-RPC transport:
from mcp.server import FastMCP mcp = FastMCP("my-tool-server") @mcp.tool() def search(query: str, limit: int = 10) -> list[dict]: """Full-text search over the knowledge base.""" return perform_search(query, limit) if __name__ == "__main__": mcp.run() # stdio transport by default
Structured Output with Pydantic
Both the Anthropic and OpenAI SDKs accept Pydantic models directly for structured output:
from anthropic import Anthropic from pydantic import BaseModel class CodeReview(BaseModel): summary: str issues: list[str] severity: str # "low" | "medium" | "high" approved: bool client = Anthropic() response = client.messages.create( model="claude-opus-4-5", max_tokens=1024, messages=[{"role": "user", "content": "Review this diff: ..."}], ) # Structured parsing with tool_use or beta structured output
Key Libraries (2026)
| Library | Purpose | Notes |
|---|---|---|
anthropic |
Claude API client | Streaming, tooluse, vision |
openai |
OpenAI API client | Compatible with many providers |
mcp |
MCP server/client | FastMCP high-level API |
pydantic v2 |
Validation + serialization | Rust core, 5–50x faster than v1 |
httpx |
Async HTTP | Replaces requests in async contexts |
rich |
Terminal output | Progress, tables, syntax highlight |
typer |
CLI framework | Built on click + type hints |
