Skip to Content
Course content

36: Tuples and Immutability

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was reviewing a pull request for a logistics project where a junior dev was handling GPS coordinates. They were using lists to store the (latitude, longitude) pairs for a set of delivery waypoints. It seemed fine at first, but a few weeks later, we hit a bug that was a nightmare to track down. A helper function, designed to calculate distance, was accidentally modifying the longitude of the original waypoint list using an index assignment. Because lists are mutable, the coordinates were changing silently in the background, and suddenly our trucks were being routed into the middle of the Atlantic Ocean.

That's the exact moment where you realize that "being able to change things" isn't always a feature—sometimes, it's a liability. This is why we have tuples.

The Guardrails of Immutability

On the surface, a tuple looks like a list that uses parentheses () instead of square brackets []. But the fundamental difference is that tuples are immutable. Once you define a tuple, you cannot add, remove, or change its elements. If you try to assign a new value to an index, Python will throw a TypeError immediately.

# This is a list (Mutable)
coordinates_list = [40.7128, -74.0060]
coordinates_list[0] = 40.7306  # Works perfectly fine

# This is a tuple (Immutable)
coordinates_tuple = (40.7128, -74.0060)
coordinates_tuple[0] = 40.7306  # Raises TypeError: 'tuple' object does not support item assignment

I like to think of tuples as a way of signaling intent. When I see a tuple in a codebase, I know that the developer intended for that data to remain constant throughout its lifecycle. It's a contract that prevents the "Atlantic Ocean" bug I mentioned earlier.

Unpacking and Returning Multiple Values

One of the most "Pythonic" uses of tuples is unpacking. You've probably seen functions that return more than one value; in reality, Python is actually returning a single tuple, which you can then "unpack" into individual variables in one line.

def get_user_stats():
    # Simulating a database call
    return ("Alice", 2500, "Gold")  # Returning a tuple

# Unpacking the tuple directly into variables
username, score, rank = get_user_stats()

print(f"{username} has a score of {score} and is ranked {rank}.")

This is significantly cleaner than accessing indices like stats[0] and stats[1], which makes the code harder to read and maintain. If you only need some of the values, I usually recommend using an underscore _ for the ones you intend to ignore, which tells other engineers that the omission is intentional.

Tuples as Dictionary Keys

Here is a technical detail that often trips people up: because tuples are immutable, they are "hashable." This means you can use a tuple as a key in a dictionary, whereas a list would cause a TypeError.

Imagine you're building a grid-based game. You can't use a list [x, y] as a key to store what's at that position, but a tuple (x, y) works perfectly. This allows you to map specific coordinate pairs to values without worrying that the coordinates will change and "lose" the reference in your dictionary.




📋 Practical Task

Exercise: Implementing a Fixed-Coordinate Route Guard

You are building a navigation system for a drone. The drone has a set of "No-Fly Zone" centers that must never be altered during the program's execution. If these coordinates were changed, the drone could accidentally enter restricted airspace.

Your Task:

  1. Create a list of tuples called NO_FLY_ZONES. Each tuple should contain two floats representing (latitude, longitude). Add at least three different zones.
  2. Write a function called check_proximity(current_location, zones).
    • current_location should be a tuple.
    • The function should loop through the zones and print "Warning: Near Restricted Airspace!" if the current_location matches any of the tuples in the NO_FLY_ZONES list exactly.
  3. Inside your script, intentionally try to change the latitude of the first No-Fly Zone (e.g., NO_FLY_ZONES[0][0] = 12.34) and wrap this in a try/except block to catch the TypeError, printing a message that confirms the data is protected.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.