Tuples are not 'lists you can't change'
The first beginner take on tuples — "they're like lists but immutable" — misses the point. The reason tuples exist is to express something different: a fixed-shape record. A list is "these are all the same kind of thing in some order" (a sequence of orders, a list of users). A tuple is "these are the parts of one thing" (an x/y coordinate, a row from a database). The shapes are different, and Python uses both shapes idiomatically.
Creating tuples — the comma is the operator
The parentheses you usually see around tuples are optional. The thing that makes a tuple is the comma. x = 1, 2, 3 is a tuple. x = (1, 2, 3) is the same tuple — the parens just disambiguate. The exception: an empty tuple does need parens (()), and a single-element tuple needs a trailing comma ((1,)) — without the comma it's just a parenthesized expression.
Unpacking — Python's quiet superpower
Tuple unpacking is one of those features that, once you internalize it, you start to use everywhere. x, y = 1, 2 swaps two variables in one line. Functions can return multiple values just by returning a tuple. Iterators of tuples can be unpacked in a for loop. The * in first, *rest = my_list grabs "the rest of the elements" into a list.
Immutability — what it really means
You can't reassign elements of a tuple. t[0] = 99 raises TypeError. But if a tuple contains a mutable object, that object is still mutable. t = ([1, 2], 3) — you can't replace t[0], but you can do t[0].append(99). The tuple's structure is frozen; what's inside the structure may not be.
(1, 2, 3) works as a key. ([1, 2], 3) does not — the inner list breaks the chain.
namedtuple — when a tuple needs names
Indexing into a tuple by position works, but it doesn't read well: row[3] doesn't tell you what 3 means. collections.namedtuple gives a tuple subclass where each field has a name. You still get tuple-ness (immutable, hashable, indexable, unpackable), and you also get row.email. Modern Python has even better options for this — typing.NamedTuple with type hints, and dataclass(frozen=True) — and we'll see those in the OOP track. namedtuple is still the cheapest, fastest way to give a tuple field names.