A practical, mental-model focused introduction to Python: syntax minimalism, data handling, control flow, functions, error handling, packaging, and idiomatic style โ with interactive building blocks.
Python emphasizes readability + object model simplicity.
Names point to objects; object carries type. Rebinding doesn't mutate existing objects.
Functions, classes, modules โ all have identities & attributes. Int immutability โ arithmetic returns new objects.
Block scope defined purely via indentation; no braces. Mixed tabs/spaces = errors.
Immutable: int, str, tuple, frozenset. Mutable: list, dict, set. Mutation side-effects propagate references.
#!/usr/bin/env python3
"""Greet user with friendly message."""
import math
def circle_area(r: float) -> float:
return math.pi * r * r
if __name__ == "__main__":
r = 3
print(f"Area: {circle_area(r):.2f}")
__name__ == "__main__" gate prevents side-effect logic on import.from __future__ import annotations for forward refs.python -m venv .venv โ activate, then pip install.requirements.txt.Decompose for clarity; embrace pure logic where possible.
def f(*, mode)).map, sorted(key=...).def append_item(x, bucket=[]):
bucket.append(x)
return bucket
print(append_item(1)) # [1]
print(append_item(2)) # [1, 2] (surprise!)
# Correct
def append_item_safe(x, bucket=None):
if bucket is None:
bucket = []
bucket.append(x)
return bucket
Choose representation to optimize clarity + complexity.
Resizable array. Append amortized O(1). Slice copies; list comps > manual loops.
Immutable fixed-size sequence. Hashable if elements hashable โ safe dict key.
Hash map. O(1) average access. Keys must be hashable + stable.
Unique membership. O(1) avg add/test. Great for dedupe and membership tests.
Auto boilerplate (__init__, repr). Use for plain data containers.
O(1) append/pop both ends. Use instead of list for queue semantics.
# Imperative
squares = []
for i in range(10):
squares.append(i * i)
# Comprehension
squares = [i * i for i in range(10)]
# With condition
evens = [x for x in squares if x % 2 == 0]
Use exceptions for exceptional control โ not ordinary flow.
raise ValueError("invalid")raise NewError() from errfinally for cleanup.except: (swallows bugs).try:
risky()
except (IOError, ValueError) as e:
logger.error("Failure: %s", e)
raise
else:
print("Success")
finally:
cleanup()
Client-side helpers simulate transformations (no real Python execution sandbox).
High-frequency forms.
int("10") # 10
float("3.14")
str(42) # "42"
len(seq)
# Truthiness: 0, '', [], {}, None => Falselst = [1,2,3]
tpl = (1,2,3)
set_ = {1,2,3}
d = {"a":1, "b":2}
# Unpacking
x, y, *rest = lstfor i in range(5):
...
while cond:
...
if x > 0:
...
elif x == 0:
...
else:
...def f(a, b=10, *args, **kw):
return a + b
(lambda x: x+1)(2)
from dataclasses import dataclass
@dataclass
class User:
name: str
age: inttry:
1/0
except ZeroDivisionError:
...
raise RuntimeError("fail")
assert cond, "msg"with open("data.txt") as f:
data = f.read().splitlines()Track what you can explain, not just run.
Enables interning, hashing, safe sharing, and avoids unintended alias mutation; improves performance & security.
Tuple for fixed-size heterogeneous record semantics & hashable keys; list for dynamic sequence operations.
Improved readability and single-pass expression of intent; avoids lambda noise when simple transformations.
Module = single .py file; package = directory with __init__.py (namespace). Packages enable structured imports & distribution.