Skip to content

Python Essentials

The Python you need every day for data science and AI work: variables, built-in types, control flow, functions, comprehensions, generators, exceptions, files, modules, and decorators.

Table of Contents

Installation / Setup

Python 3.10+ is recommended for data science (3.12 is current). Manage environments with virtual environments and uv or pip.

python --version          # check version
python script.py          # run a script
python -c "print(1+1)"    # run a one-liner
python                    # start the REPL
Setup task Command
Install packages pip install pandas numpy or uv add pandas numpy
Notebooks pip install jupyter → jupyter lab
Interactive data exploration pip install ipython → ipython

Core Syntax

Variables

Dynamic typing with snake_case names. No declaration needed.

x = 42          # int
name = "iris"   # str
ratio = 0.93    # float

Data types

Type Example Notes
int 42, -7 Arbitrary precision
float 3.14, 1e-5 IEEE-754 double
bool True, False Subclass of int
str "hi" Immutable, Unicode
bytes b"hi" Raw bytes
list [1, 2] Mutable, ordered
tuple (1, 2) Immutable, ordered
set {1, 2} Unique, unordered
dict {"a": 1} Key → value
NoneType None "No value"
type(3.14)      # <class 'float'>
isinstance([], list)  # True

Operators

Category Operators
Arithmetic + - * / // % **
Comparison == != < > <= >=
Logical and or not
Identity is is not
Membership in not in
Assignment = += -= *= /= //= %= **=
Bitwise & \| ^ ~ << >>
7 // 2 == 3     # floor division
7 % 2 == 1      # modulo (remainder)
2 ** 10 == 1024 # power
[1, 2] + [3]    # [1, 2, 3] concatenation
3 in [1, 2, 3]  # True membership

Strings

name = "Ada"
f"Hi {name}, score: {0.934:.2f}"   # f-strings: preferred formatting
name.upper()                        # 'ADA'
"a,b,c".split(",")                  # ['a', 'b', 'c']
" ".join(["a", "b"])                # 'a b'
"  x  ".strip()                     # 'x'
"hi".replace("i", "ello")           # 'hello'
"file.csv".endswith(".csv")         # True
r"\d+"                              # raw string: no escape processing

Lists, tuples, sets, dicts

xs = [3, 1, 2]
xs.append(4)            # [3, 1, 2, 4]
xs[0]                   # 3
xs[-1]                  # 4 (last element)
xs[1:3]                 # [1, 2] slice
xs.sort()               # in-place; sorted(xs) returns new
xs.pop()                # remove & return last

t = (1, 2, 3)           # tuple: immutable
a, b, c = t             # unpacking

s = {1, 2, 2, 3}        # {1, 2, 3} — duplicates removed
{1, 2} & {2, 3}         # {2} intersection
{1, 2} | {2, 3}         # {1, 2, 3} union
{1, 2} - {2}            # {1} difference

d = {"a": 1, "b": 2}
d["c"] = 3              # add / update
d.get("z", 0)           # 0 — no KeyError
d.setdefault("k", [])   # set default if missing
list(d.items())         # [('a', 1), ...]
"a" in d                # True membership on keys

Conditions

if x > 0:
    ...
elif x == 0:
    ...
else:
    ...

"adult" if age >= 18 else "minor"    # ternary

match status:            # structural pattern matching (3.10+)
    case 200:
        print("ok")
    case 404 | 500:
        print("error")
    case _:
        print("other")

Truthiness: 0, "", [], {}, set(), None are falsy; everything else is truthy.

Loops

for i in range(5):            # 0..4
    ...
for i, row in enumerate(rows):   # index + value
    ...
for a, b in zip(xs, ys):      # pair up iterables
    ...
while x < 10:
    x += 1
    if x == 5:
        break                 # exit loop
else:
    print("no break hit")     # runs only if no break

Common Operations

Comprehensions

[x ** 2 for x in range(5)]            # [0, 1, 4, 9, 16]
[x for x in nums if x > 0]            # filter
{k: v * 2 for k, v in d.items()}      # dict comprehension
{x % 3 for x in nums}                 # set comprehension
(x for x in nums)                     # generator expression (lazy!)

Functions

def score(y_true, y_pred, *, verbose=False):   # * = keyword-only after it
    """Compute accuracy."""
    return sum(a == b for a, b in zip(y_true, y_pred)) / len(y_true)

def f(a, b=10, *args, **kwargs):      # defaults, varargs, keywords
    ...
  • Positional defaults must come before *args; **kwargs captures named extras.
  • Functions are first-class: store, pass, and return them.
  • Lambdas for short inline functions: sorted(items, key=lambda x: x["age"]).

Iterators & generators

it = iter([1, 2, 3])
next(it)               # 1; raises StopIteration when exhausted

def read_chunks(path, size=1024):     # generator: lazy, memory-friendly
    with open(path) as f:
        while chunk := f.read(size):
            yield chunk

sum(x for x in range(1_000_000))      # generator expression: no giant list

Exceptions

try:
    result = risky_operation()
except ValueError as e:
    print("bad value:", e)
except (TypeError, KeyError):
    print("type or key problem")
else:
    print("no error — use result here")
finally:
    cleanup()                # always runs

raise ValueError("invalid input")     # raise your own

Files

with open("data.csv", encoding="utf-8") as f:   # with = auto-close
    for line in f:
        ...

# Pathlib is the modern way (see python-standard-library.md)
from pathlib import Path
text = Path("data.csv").read_text(encoding="utf-8")
Path("out").mkdir(exist_ok=True)
Mode Meaning
"r" Read (default)
"w" Write (truncates)
"a" Append
"rb" / "wb" Binary read / write

Modules & packages

import numpy as np                  # alias
from sklearn.model_selection import train_test_split
from package.module import thing    # package = dir with __init__.py

if __name__ == "__main__":          # runs only when executed directly
    main()

Decorators

import functools

def timer(func):
    @functools.wraps(func)              # preserve name/docstring
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        print(f"{func.__name__}: {time.perf_counter() - start:.4f}s")
        return result
    return wrapper

@timer
def train(model): ...

Built-in decorators: @staticmethod, @classmethod, @property, @dataclass, @functools.lru_cache, @contextlib.contextmanager (see OOP and standard library).

Context managers

with open(...) as f: ...        # resource auto-released
with np.errstate(divide="ignore"): ...   # NumPy warning control

from contextlib import contextmanager

@contextmanager
def session():
    print("open")
    try:
        yield
    finally:
        print("close")

Practical Examples

1. Compute summary stats from a CSV-like file

import csv
from collections import Counter

with open("sales.csv", newline="", encoding="utf-8") as f:
    rows = list(csv.DictReader(f))

total = sum(float(r["amount"]) for r in rows)
by_region = Counter(r["region"] for r in rows)
print(total, by_region.most_common(3))

2. Filter and rank records

users = [
    {"name": "Ada", "score": 92},
    {"name": "Lin", "score": 85},
    {"name": "Sam", "score": 78},
]
top = [u for u in sorted(users, key=lambda u: u["score"], reverse=True) if u["score"] >= 80]

3. Stream a large file in chunks

def rows(path, batch=1000):
    buffer = []
    with open(path, encoding="utf-8") as f:
        for line in f:
            buffer.append(line.strip())
            if len(buffer) >= batch:
                yield buffer
                buffer = []
    if buffer:
        yield buffer

Common Patterns

# Default-argument idiom (never use mutable defaults directly)
def add(x, seen=None):
    seen = [] if seen is None else seen
    ...

# Dict lookups with defaults
counts = {}
counts[word] = counts.get(word, 0) + 1

# any / all for bulk checks
all(x > 0 for x in nums)
any(row["status"] == "failed" for row in rows)

# min / max with key
cheapest = min(items, key=lambda i: i["price"])

# Swap / unpack
a, b = b, a
first, *rest, last = xs

Best Practices

  • Follow PEP 8: snake_case names, 4-space indentation, blank lines between functions.
  • Prefer f-strings over % formatting and .format().
  • Use with for files and resources — never open without closing.
  • Prefer generators over lists for large data (memory).
  • Write type hints on public functions — see python-typing.md.
  • Keep functions small and single-purpose; one function = one job.
  • Use pathlib instead of string path manipulation.
  • Always work inside a virtual environment for projects.

Common Mistakes

Mistake Consequence Correct approach
Mutable default argument State leaks across calls Default to None, create inside
== vs is is compares identity, not value == for values; is None for None
Modifying a list while iterating Skipped / repeated items Iterate over a copy: for x in xs[:]
xs = ys then mutate Both variables change xs = ys.copy() or copy.deepcopy
String + in a loop O(n²) — slow on large data "".join(parts)
Shadowing builtins (list, dict, sum) Confusing bugs Rename: rows, data, total

Troubleshooting

Symptom Cause Fix
ModuleNotFoundError: No module named 'pandas' Package not installed / wrong environment pip install pandas; check active venv
IndentationError Mixed tabs/spaces Use 4 spaces consistently
UnboundLocalError: local variable 'x' referenced... Assigned inside function shadows outer Use global/nonlocal or rename
TypeError: 'int' object is not subscriptable Indexing a non-sequence Check the value's actual type
StopIteration next() on exhausted iterator Catch it or use next(it, default)
FileNotFoundError Path wrong or relative to wrong cwd Use Path(__file__).parent for project-relative paths

Official Documentation

Last Updated

2026-09-06 — Initial version.