🍎 macOS Edition

Python
from first principles

A complete syntax reference built for Mac users — from installing Python with Homebrew through advanced language features, with every example runnable in your Terminal.

Python 3.12+ macOS 13 Ventura + Terminal · VS Code · PyCharm 15 sections Hello World to Decorators

00 — Setting Up Python on Your Mac

macOS ships with Python 3 but it's often outdated. The best path: install Homebrew (the Mac package manager), use it to install pyenv (Python version manager), then install the latest Python. Takes about 5 minutes.

Step 1

Install Homebrew

Homebrew is the missing package manager for macOS. Open Terminal (⌘ Space → "Terminal") and run the one-liner from brew.sh.

Step 2

Install pyenv

pyenv lets you install and switch between multiple Python versions. Essential for real projects. Install via Homebrew.

Step 3

Install Python

Use pyenv to install the latest stable Python. Set it as your global default. Confirm with python3 --version.

Step 4

Pick an Editor

VS Code (free, excellent Python extension) or PyCharm CE (free, Python-first IDE). Both are in Homebrew: brew install --cask visual-studio-code.

Terminal — zsh
# Step 1 — Install Homebrew (paste this entire line)
% /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Step 2 — Install pyenv
% brew install pyenv
% echo 'eval "$(pyenv init -)"' >> ~/.zshrc && source ~/.zshrc
# Step 3 — Install latest Python and set as default
% pyenv install 3.12.3
% pyenv global 3.12.3
% python3 --version
Python 3.12.3
# Start the interactive REPL (Read-Eval-Print Loop)
% python3
>>> print("Hello, Mac!") # ⌃D to exit
Hello, Mac!
Virtual environments — always use one per project.
python3 -m venv .venv creates an isolated environment.
source .venv/bin/activate activates it — your terminal prompt gains (.venv).
deactivate exits it. This keeps every project's packages separate.
01

Variables & Types

Python is dynamically typed — you assign a value and Python infers the type. No int x = 0 declarations. Types are objects: int, float, bool, str, None, list, dict, tuple, set.

dynamic typing int / float / bool str / None type() / isinstance() type hints

No declaration keyword

Write name = "Alice" — no var, let, or int. The type is attached to the value, not the variable. The same variable can hold different types at different times (though doing so is bad practice).

Boolean literals are capitalised

True and False — not true/false. Python's null is None, not null or nil.

Type system

int Arbitrary-precision integer. No overflow. 42, -7, 1_000_000 float 64-bit IEEE 754 double. 3.14, 1.5e10, float('inf') complex Complex numbers. 3+4j bool Subclass of int. True == 1, False == 0 str Immutable Unicode text. "hello" None Null sentinel. The sole instance of NoneType bytes Raw binary data. b"data"

Truthiness

Everything is truthy except: None, False, 0, 0.0, "", [], {}, ().

So if my_list: means "if the list is non-empty" — idiomatic Python.

Type hints (Python 3.5+)

Hints are documentation for tools (mypy, your IDE). Python ignores them at runtime. Write def greet(name: str) -> str: to document intent without enforcing it.

💡 In the Terminal REPL, type type(42) and press Return to see <class 'int'>. Experiment freely — the REPL never saves state between sessions.
variables.py
02

Strings & f-strings

Strings are immutable sequences of Unicode characters. Index them like a list. Slice them. Iterate over them. Python 3.6+ f-strings are the best way to embed values in text.

str methods f-strings slicing raw strings triple quotes

Three quoting styles

'single' and "double" are identical — use whichever avoids escaping. """triple""" spans multiple lines and is also used for docstrings.

f-strings — the preferred formatting method

f"Hello, {name}!" evaluates the expression inside {} and converts it to a string. You can put any expression inside: f"{2 + 2}", f"{obj.method()}".

Format specifiers go after a colon: f"{value:.2f}" → 2 decimal places. f"{n:05d}" → zero-padded 5-digit int.

Slicing — [start:stop:step]

All three parts are optional. Negative indices count from the end: s[-1] is the last character. s[::-1] reverses the string.

Raw strings

Prefix r: r"C:\Users\Alice" — backslashes are literal. Essential for file paths on Windows and for regex patterns.

On Mac, file paths use forward slashes: "/Users/alice/Documents" — no backslashes needed. Use pathlib.Path to write path code that works on all platforms.

Essential string methods

  • .strip()Remove leading/trailing whitespace. .lstrip() / .rstrip() for one side.
  • .split()Split on whitespace (default) or delimiter. Returns a list.
  • .join()Opposite of split: ", ".join(["a","b","c"])"a, b, c"
  • .replace()Replace all occurrences: "hello".replace("l","r")"herro"
  • .startswith()Returns bool. Useful for if line.startswith("#"):
  • .upper() .lower()Case conversion. .title() for Title Case.
strings.py
03

Control Flow

Python uses indentation (4 spaces) instead of braces {}. There are no semicolons. Colons (:) end condition lines. Python 3.10+ adds match/case.

if / elif / else for / range while / break / continue match / case ternary expression

Indentation IS syntax

Unlike C, Java, or JavaScript, indentation is not optional. Inconsistent indentation raises IndentationError. The standard is 4 spaces — configure your editor to insert spaces on Tab.

⚙️ In VS Code on Mac: ⌘ , → search "Tab Size" → set to 4. Or set "Editor: Insert Spaces" to true. Python will thank you.

for loops iterate directly

Python's for loop iterates over any iterable — lists, strings, dicts, generators. You rarely need an index counter. When you do, use enumerate().

range(start, stop, step)

Generates integers lazily. range(10) → 0–9. range(2, 10, 2) → 2, 4, 6, 8. range(10, 0, -1) → 10 down to 1.

for...else — a Python exclusive

The else block on a for loop runs only if the loop completed without hitting a break. Useful for "search and report not found" patterns.

match / case (Python 3.10+)

Structural pattern matching. More powerful than a switch statement — it can match values, types, sequences, and structures with guard clauses (if). Wildcard: case _.

Common mistake: using == instead of is for None checks. Always write if x is None: — not if x == None:.
control_flow.py
04

Functions

Functions are first-class objects — they can be stored in variables, passed as arguments, and returned from other functions. Python supports default arguments, keyword arguments, variadic arguments, and closures.

def / return default args *args / **kwargs lambda closures type hints

Defining a function

def name(params): ... — the body must be indented. return exits with a value. A function without return returns None.

Default arguments

def greet(name, greeting="Hello"): — parameters with defaults are optional. Critical rule: default arguments are evaluated once at function definition, not each call. Never write def f(x, items=[]): — that list is shared across all calls.

Mutable default gotcha: def add(item, lst=[]): — the same list is reused on every call. Write def add(item, lst=None): lst = lst or [] instead.

*args and **kwargs

  • *argsCollects extra positional arguments into a tuple. def total(*nums): return sum(nums)
  • **kwargsCollects extra keyword arguments into a dict. def config(**opts): ...
  • *A bare * in the signature makes all following params keyword-only: def f(a, *, b):b must be named at call site.

lambda — anonymous functions

lambda x: x * 2 is a single-expression function. Useful as a key= argument to sorted()/max() etc. Prefer a named def for anything more complex.

Closures

An inner function that references a variable from its enclosing scope creates a closure — the variable is captured. Used to build factory functions.

functions.py
05

Lists & Tuples

list — ordered, mutable, allows duplicates. tuple — ordered, immutable, allows duplicates. Use tuples for records whose fields won't change; lists for collections you'll modify.

list [] tuple () slicing unpacking sorted / sort namedtuple

List methods

.append(x) Add x to end — O(1) .extend(it) Add all items from iterable — like += .insert(i,x)Insert x before index i — O(n) .pop(i) Remove and return item at index i (default: last) .remove(x) Remove first occurrence of x — raises ValueError if absent .sort() Sort in-place. key= for custom ordering. reverse=True. .index(x) Index of first x — raises ValueError if absent .count(x) Count occurrences of x .copy() Shallow copy — same as lst[:]

sorted() vs .sort()

sorted(lst) returns a new sorted list, leaving the original unchanged. lst.sort() sorts in-place and returns None. Prefer sorted() when you want to keep the original.

Tuple unpacking

x, y = (3, 4) — assigns each element to a variable. first, *rest = [1,2,3,4] uses the starred expression to collect the tail. Useful for swapping: a, b = b, a.

namedtuple — a lightweight struct

from collections import namedtuple
Point = namedtuple("Point", ["x","y"])
Access by name: p.x and by index: p[0]. Immutable like a regular tuple.

lists_tuples.py
06

Dicts & Sets

dict — unordered key→value mapping (ordered by insertion since Python 3.7). set — unordered collection of unique hashable values. Both have O(1) average lookup.

dict {} set {} dict.get() defaultdict set operations dict merge |

Dict access patterns

  • d[key]Direct access — raises KeyError if key absent. Use when you expect the key to exist.
  • .get(k,v)Safe access — returns None (or default v) if key missing. Never raises. Preferred for uncertain keys.
  • .setdefault(k,v)Returns value if key exists; otherwise inserts v and returns it. Useful for building dicts of lists.
  • k in dMembership test — O(1). Tests keys only. k in d.values() is O(n).
  • .items()Returns view of (key, value) pairs. Use in for k, v in d.items():

Dict merging (Python 3.9+)

merged = dict1 | dict2 creates a new merged dict. dict1 |= dict2 updates in-place. Before 3.9: {**dict1, **dict2}.

Set operations

a | b Union — all elements in either a & b Intersection — elements in both a - b Difference — in a but not b a ^ b Symmetric difference — in one but not both a <= b Subset check — all of a is in b
Empty set: {} creates an empty dict. For an empty set, write set(). This is a famous Python gotcha.

defaultdict

from collections import defaultdict
dd = defaultdict(list) — accessing a missing key auto-creates it with list() as the default value. Great for grouping: dd["key"].append(item) without checking if the key exists first.

dicts_sets.py
07

Comprehensions

The most distinctively Pythonic syntax. Build lists, dicts, and sets in one expressive line. Comprehensions are faster than equivalent for loops and are the preferred way to transform or filter collections.

list comprehension dict comprehension set comprehension generator expression nested comprehension

The pattern

[expression for item in iterable if condition]

All three parts: the expression transforms each item, the for iterates, and the if filters. The if clause is optional.

Four flavours

[…] List comprehension — builds a list eagerly {k: v}Dict comprehension — builds a dict {…} Set comprehension — builds a set (deduplicates) (…) Generator expression — lazy, no memory until iterated

Generator expressions save memory

sum(x**2 for x in range(1_000_000)) — the generator produces values one at a time without building a million-element list. Use generators inside function calls like sum(), max(), any(), all().

Nested comprehension — flattening

[x for row in matrix for x in row] — two for clauses flatten a 2D list. Read left to right as nested loops: outer loop first, inner second.

Readability rule: If your comprehension requires mental effort to parse, break it into a for loop. The goal is clarity, not brevity.
comprehensions.py
08

Classes & OOP

Everything in Python is an object — integers, strings, functions, modules. You define your own types with class. The first argument to every method is self (the instance), passed automatically.

class / __init__ self / cls @property @classmethod / @staticmethod @dataclass dunder methods

__init__ — the initialiser

Called when you write Dog("Rex", "Labrador"). Sets up instance variables on self. It is not a constructor — the object is already created when __init__ runs.

Instance vs class variables

self.name Instance variable — unique to each object. Set in __init__. Dog.count Class variable — shared by all instances. Declared in class body.

@property

Turns a method into an attribute: callers write dog.weight instead of dog.weight(). Add a setter with @weight.setter to validate on assignment.

@classmethod vs @staticmethod

  • @classmethodFirst arg is cls (the class). Use as alternate constructors: Dog.from_dict(data).
  • @staticmethodNo implicit first arg. Just a function attached to the class for organisation. Doesn't know about self or cls.

@dataclass — zero-boilerplate classes

Automatically generates __init__, __repr__, and __eq__. Write x: type = default for each field. Use field(default_factory=list) for mutable defaults.

Key dunder methods

__str__ Human-readable string via str(obj) / print(obj) __repr__ Developer string via repr(obj) — should be eval-able __len__ Called by len(obj) __eq__ Called by == operator __lt__ Called by < — enables sorted() __getitem__Called by obj[key] __contains__Called by x in obj __call__ Makes object callable: obj()
classes.py
09

Inheritance

Python supports single and multiple inheritance. super() calls the parent class. Python uses the C3 linearisation algorithm (MRO) to resolve method order in multiple inheritance.

class Child(Parent) super() isinstance / issubclass multiple inheritance abstract base class

Subclassing

class Dog(Animal):Dog inherits all methods and class variables from Animal. Override by defining the same method name in the subclass.

super() — calling the parent

super().__init__(name, sound) calls Animal.__init__. Without super(), the parent's __init__ is skipped and instance variables won't be set.

isinstance vs type()

isinstance(rex, Animal) returns True for instances of Animal and any subclass. type(rex) is Animal returns False for subclass instances. Almost always prefer isinstance().

Abstract base classes

from abc import ABC, abstractmethod — a class inheriting ABC can declare @abstractmethod methods that subclasses must implement. Instantiating the ABC directly raises TypeError.

Multiple inheritance & MRO

class C(A, B): — Python resolves method lookup in a consistent left-to-right order (C3). Inspect with C.__mro__. Keep multiple inheritance shallow; prefer composition for complex cases.

inheritance.py
10

Exception Handling

Python uses exceptions for all error handling. The full syntax is try / except / else / finally. Catch the most specific exceptions first. Never use bare except: without a type.

try / except else / finally raise custom exceptions exception hierarchy

try / except / else / finally

  • tryThe block that might raise an exception.
  • exceptCatches a specific exception type. List multiple types in a tuple: except (TypeError, ValueError):
  • elseRuns only if the try block succeeded (no exception). Cleaner than putting success code in try.
  • finallyAlways runs — cleanup code (close files, release locks). Runs even if an exception was raised and not caught.

raise — throwing exceptions

raise ValueError("must be positive") — raises with a message. Plain raise inside an except block re-raises the current exception.

Custom exceptions

Inherit from Exception (or a more specific built-in). Add custom attributes in __init__. Use a hierarchy for your app: class AppError(Exception) as the base, then class NotFoundError(AppError).

Anti-patterns:
except: with no type — catches even SystemExit and KeyboardInterrupt.
except Exception: everywhere — hides bugs.
• Empty except blocks — silently swallows errors.

Common exceptions

TypeRaised when
ValueErrorRight type, wrong value: int("abc")
TypeErrorWrong type: "a" + 1
KeyErrorDict key missing: d["x"]
IndexErrorList index out of range
AttributeErrorObject has no such attribute
FileNotFoundErrorFile doesn't exist
ZeroDivisionError10 / 0
StopIterationGenerator/iterator exhausted
exceptions.py
11

File I/O

Always use the with context manager — it guarantees the file is closed even if an exception occurs. Use pathlib.Path for path manipulation instead of string concatenation.

open() / with read / write modes pathlib.Path json / csv encoding UTF-8

File modes

"r" Read (default). File must exist. "w" Write — creates or overwrites. "a" Append — creates or adds to end. "x" Exclusive create — raises error if file exists. "rb"/"wb"Binary mode — no text encoding. "r+" Read + write without truncating.
Always specify encoding: open("f.txt", encoding="utf-8"). On Mac the default is usually UTF-8, but specifying it makes code portable.

pathlib — the modern way

Use Path instead of string concatenation for paths. The / operator joins paths: Path.home() / "Documents" / "data.txt" — this works on Mac, Linux, and Windows.

Path.home() Mac: /Users/alice p.exists() Bool — does the path exist? p.is_file() Bool — is it a regular file? p.stem Filename without extension p.suffix Extension including dot: ".py" p.glob("*.py") Iterator of matching paths p.read_text() Read entire file as string p.write_text(s) Write string to file
📂 On Mac, Path.home() returns /Users/yourname. Drag a folder from Finder into Terminal to paste its full path — useful for getting exact paths.
file_io.py
12

Modules & Packages

Any .py file is a module. A directory with __init__.py is a package. Always use virtual environments — one per project — to keep dependencies isolated.

import styles venv pip __name__ guard relative imports

Import styles

import math Import module — use as math.sqrt() import math as m Alias — use as m.sqrt() from math import sqrtImport name directly — use as sqrt() from math import * Import all — avoid, pollutes namespace

Virtual environments on Mac

Terminal — zsh
% mkdir myproject && cd myproject
% python3 -m venv .venv # create environment
% source .venv/bin/activate # activate it
(.venv) % # notice the prefix — you're inside the venv
(.venv) % pip install requests
(.venv) % pip freeze > requirements.txt
(.venv) % deactivate # exit the venv

Package structure

myproject/
├── .venv/
├── mypackage/
│   ├── __init__.py      ← makes it a package
│   ├── utils.py
│   └── models/
│       ├── __init__.py
│       └── user.py
├── main.py
└── requirements.txt

__name__ guard — always use it

if __name__ == "__main__": — code inside only runs when the file is executed directly, not when imported as a module. Without it, importing your module would execute its side effects.

modules.py
13

Decorators

A decorator is a function that wraps another function. The @ syntax is shorthand: @timer above def f() is exactly f = timer(f). Used for logging, caching, auth, timing, and more.

@decorator syntax functools.wraps decorator with args @lru_cache stacking decorators

Anatomy of a decorator

Three layers: the outer function receives the function to wrap, the inner wrapper adds before/after logic and calls the original, and the outer function returns wrapper.

@functools.wraps — always use it

Without @functools.wraps(func), your decorator replaces func.__name__ and func.__doc__ with "wrapper". @wraps copies the original's metadata onto the wrapper, preserving tracebacks and documentation.

Decorators with arguments

Add another level: the outermost function takes the arguments and returns the actual decorator. @retry(times=3) requires: outer function retry(times) → returns decorator → returns wrapper.

Stacking decorators

Decorators are applied bottom-up. @a above @b above def f means f = a(b(f)).

@functools.lru_cache

Built-in memoisation decorator. Caches function results by arguments. @lru_cache(maxsize=128) — ideal for recursive functions like Fibonacci. @cache (Python 3.9+) is lru_cache with unlimited size.

decorators.py
14

Generators & Context Managers

yield turns a function into a generator — values are produced lazily, one at a time. Context managers (with) manage resources cleanly using __enter__ / __exit__.

yield yield from next() / StopIteration contextmanager itertools

How generators work

When Python sees yield, calling the function returns a generator object without executing any code. Each next() call runs until the next yield, then suspends. When the function returns, StopIteration is raised.

yield from — delegating

yield from other_iterable delegates to another iterable, yielding each of its values. Cleaner than a nested for loop with yield.

Context managers

The with statement calls __enter__ on entry and __exit__ on exit — even if an exception occurs. Implement with a class or with @contextlib.contextmanager.

@contextmanager — the easy way

Write a generator function with a single yield inside a try/finally. Code before yield is __enter__; code after is __exit__. The yielded value becomes the as target.

itertools — lazy combinatorics

chain(a,b) Combine iterables without copying islice(it,n) Take first n items lazily groupby(it,key)Group consecutive items by key product(a,b) Cartesian product (nested loops) accumulate(it)Running totals (like cumsum)
generators.py
15

Essential Standard Library

Python ships with "batteries included". These modules cover the most common needs without any pip install.

datetime collections re os / sys random json / csv subprocess

Most-used modules

ModuleUse for
datetimeDates, times, timedeltas, formatting
pathlibFile paths — use instead of os.path
osEnvironment vars, os.getcwd(), os.makedirs()
syssys.argv, sys.exit(), sys.path
jsonParse/serialise JSON data
csvRead/write CSV files
reRegular expressions
collectionsCounter, defaultdict, deque, namedtuple
itertoolsLazy iteration combinators
functoolswraps, lru_cache, partial, reduce
randomRandom numbers, choices, shuffling
mathMaths functions, math.pi, math.inf
subprocessRun shell commands from Python
loggingStructured log output (use instead of print)
threading / asyncioConcurrency — threads or async I/O
sqlite3Built-in SQL database
hashlibMD5, SHA-256 hashing
urllib / httpHTTP requests (or use httpx/requests)
🖥️ subprocess.run(["open", path]) opens a file in its default Mac app — like double-clicking in Finder. subprocess.run(["open", "-a", "Safari", url]) opens a URL.
stdlib_examples.py

Quick Reference

All 15 sections at a glance — what each covers and the key syntax to remember.

Mac Setup
Section 00

Homebrew → pyenv → Python 3.12. Always use venv. source .venv/bin/activate.

Variables & Types
Section 01

No declarations. True/False/None capitalised. isinstance(x, int). Type hints are optional documentation.

Strings & f-strings
Section 02

Immutable sequences. f"{expr}" preferred. Slice with [start:stop:step]. Raw strings: r"...".

Control Flow
Section 03

Indentation = syntax. for x in iterable. enumerate(). match/case (3.10+). for/else.

Functions
Section 04

First-class objects. Default args. *args/**kwargs. lambda x: expr. Closures capture outer scope.

Lists & Tuples
Section 05

List: mutable. Tuple: immutable. sorted(lst) vs lst.sort(). Unpack: a, *rest = lst.

Dicts & Sets
Section 06

Dict: O(1) lookup. .get(k, default). Set: unique values. set() for empty. | merges (3.9+).

Comprehensions
Section 07

[e for x in it if c]. Dict: {k:v …}. Set: {e …}. Generator: (e …) — lazy.

Classes & OOP
Section 08

__init__(self). @property. @classmethod/@staticmethod. @dataclass. Dunder methods.

Inheritance
Section 09

class Dog(Animal). super().__init__(). isinstance() prefers over type(). Abstract base classes.

Exceptions
Section 10

try/except/else/finally. Catch specific types. raise ValueError("msg"). Never swallow silently.

File I/O
Section 11

with open(p, "r", encoding="utf-8"). pathlib.Path. json.load/dump. Always UTF-8.

Modules
Section 12

One venv per project. pip install. requirements.txt. if __name__ == "__main__".

Decorators
Section 13

@name = f = name(f). Always use @functools.wraps. @lru_cache for memoisation.

Generators
Section 14

yield = lazy values. yield from delegates. with = context manager. @contextmanager.

PEP 8 — The Python Style Guide (key rules for Mac editor setup)
RuleDetailMac editor tip
Indentation4 spaces — never tabsVS Code: ⌘, → Tab Size: 4, Insert Spaces: on
Line length79 chars max (99 for teams)VS Code: set "editor.rulers": [79]
Namingsnake_case vars/funcs, PascalCase classes, UPPER_CASE constants
Blank lines2 between top-level defs, 1 between methods
Importsstdlib, then third-party, then local. Alphabetical within groups.Install isort extension
QuotesConsistent single or double — your choiceBlack auto-formats to double
Auto-formatRun black or ruff formatbrew install black then black *.py
Operator reference
ArithmeticResult
10 / 33.333… (always float)
10 // 33 (floor division)
10 % 31 (modulo)
2 ** 8256 (exponentiation)
Logic/ComparisonNotes
and / or / notnot && || !
== / !=value equality
is / is notidentity — use with None
in / not inmembership test