# Tuples and Dictionaries

> Chapter 12 of the Courseiva PYTHON-PCEP curriculum — https://courseiva.com/learn/python-pcep/tuples-and-dictionaries

**Official objective:** 4.2 — Work with tuples and dictionaries: creation, access, and methods.

## Introduction

How do you store a collection of related data that must never change, like the days of the week or the RGB colour values for a pixel? And how do you look up a value by a meaningful label, like a person's name, without searching through a whole list? Tuples and dictionaries solve these two exact problems, and the PCEP-30-02 exam will test your ability to create them, access their contents, and use their built-in methods correctly.

## The Filing Cabinet and Restaurant Menu Analogy

Because you need to store a sequence of fixed items that must never change - like the weekly dinner menu your grandmother wrote on a card and pinned to the kitchen wall - a tuple is your digital equivalent. You cannot add a new dish mid-week or remove Thursday's pasta; the order and contents are frozen in time. This immutability means you can trust that the data will always be exactly as you left it, which prevents accidental bugs when multiple parts of a programme try to read the same information.

A dictionary, by contrast, is like a well-organised filing cabinet in a busy office. You store each piece of paper (a value) behind a labelled tab (a key). When a colleague asks for the quarterly sales report, you do not search through a drawer in order; you pull the tab labelled "Q4-Sales" and retrieve the file directly. You can add new tabs, remove old ones, or update the papers behind existing labels without reorganising the whole cabinet. This key-based lookup makes dictionaries incredibly fast for looking up specific pieces of data when you know the label, which is why they are used everywhere in real programmes.

## Core explanation

Think of a tuple as a fixed-length, ordered collection of items that cannot be changed after it is created. Imagine you have a variable called 'coordinates' that stores the latitude and longitude of a city. Once you set those values, you never want them accidentally altered. In Python, you write this as coordinates = (51.5074, -0.1278). The round brackets (parentheses) tell Python that this is a tuple, not a list. The items inside are separated by commas. Tuples are immutable, which means you cannot add, remove, or replace elements once the tuple exists. This immutability is the key property that distinguishes tuples from lists.

Why does immutability matter? Imagine you build a function that returns a person's birth date as a tuple: birth = (1990, 5, 15). Because the tuple is immutable, no other part of your programme can later change that birth date by mistake. This makes your code safer and easier to reason about. The exam will expect you to know that you access tuple elements using square brackets and an index (position number), starting at 0. For example, birth[0] returns 1990. You can also slice a tuple: coordinates[0:1] returns a new tuple containing just the first element.

Now consider a dictionary. A dictionary, often called a dict, is an unordered collection of key-value pairs. Each key is unique and acts like a label; each value is the data associated with that label. You create a dictionary with curly braces: person = {"name": "Alice", "age": 30, "city": "London"}. Here, "name" is a key, and "Alice" is the corresponding value. You retrieve a value by using the key inside square brackets: person["name"] returns "Alice". If you try to access a key that does not exist, Python raises a KeyError, which is a common exam trap.

Dictionaries are mutable: you can add new key-value pairs, modify existing values, and delete pairs. For example, person["job"] = "Engineer" adds a new entry, and del person["city"] removes the city key and its value. The exam tests several built-in dictionary methods that you must memorise:

- .keys() returns a view object containing all keys.
- .values() returns a view object containing all values.
- .items() returns a view object of key-value pairs as tuples.
- .get(key, default) safely returns the value for a key, or a default if the key is missing, avoiding a KeyError.
- .pop(key) removes a key and returns its value.
- .update(dict2) merges another dictionary into the current one, overwriting existing keys.

Tuples have only two methods: .count(item) returns how many times an item appears, and .index(item) returns the first position where the item is found. Because tuples are immutable, they have no methods for adding or removing elements.

The exam also covers the in operator for both types: "name" in person checks if "name" is a key in the dictionary, and "Alice" in my_tuple checks if the value is present. Understanding the difference between these two use cases is crucial: for dictionaries, in checks keys, not values.

Finally, know how to convert between types when needed. list(my_tuple) creates a new list from a tuple's elements. tuple(my_list) creates a tuple from a list. dict() can convert a list of two-element tuples (key-value pairs) into a dictionary. These conversions appear regularly in exam questions.

## Real-world context

Imagine you work for an online clothing retailer. The company stores product information in a huge database (like a warehouse of files), but when a customer visits a product page, the web application needs to load that product's data very quickly. A real IT professional uses dictionaries to solve this problem efficiently.

Here is the step-by-step scenario:

- The web application receives a request for product ID "P-1038". The product ID is a unique string, like a key in a dictionary.
- The application calls a function that queries the database and receives back a tuple containing the product's fixed attributes: ("P-1038", "Blue Denim Jacket", 59.99, 25). The tuple is a sensible choice because the product ID, name, and price should never be changed accidentally by the code. They are read-only reference data.
- The application then builds a dictionary for the current session with keys like "price", "stock_level", and "discount". It might start as: product_data = {"price": 59.99, "stock_level": 25}. The stock level changes as customers place orders, so a mutable dictionary is perfect.
- When a customer adds the jacket to their basket, the code runs: product_data["stock_level"] -= 1. That line reduces the stock count by one. A tuple would throw an error if you tried to modify it, so using a dictionary here is necessary.
- The developer also uses .get() to check if a discount exists before applying it: discount = product_data.get("discount", 0). This avoids a crash if the discount key has not been set yet.
- At the end of the day, the system might combine data from multiple sources using .update(), merging inventory updates from the warehouse with pricing updates from the marketing team.

This scenario shows how tuples and dictionaries serve complementary roles. Tuples hold stable, ordered data that should not change. Dictionaries hold mutable, labelled data that needs frequent updates. An IT professional must choose the correct type for each situation, and the PCEP-30-02 exam tests precisely this judgement.

Additionally, a common real-world use for tuples is returning multiple values from a function. For example, a function that parses a log file might return (line_number, error_code, message) as a tuple. The calling code can then unpack it: num, code, msg = parse_log(next_line). This unpacking technique is frequently tested on the exam.

## Exam focus

The PCEP-30-02 exam tests tuples and dictionaries in a very specific way. You will see questions that require you to predict the output of code snippets, identify correct syntax, and choose between similar data types. Here is precisely what you need to know:

Exam topics you must master:

- Creating a tuple: using parentheses, with or without a trailing comma for single-element tuples.
- Creating a dictionary: curly braces with colons between keys and values.
- Accessing elements: square-bracket indexing for tuples, square-bracket key lookup for dictionaries.
- The .get() method: its purpose is to avoid KeyError; it can return a default value.
- The .keys(), .values(), and .items() methods: they return view objects, not lists, but can be converted to lists.
- The .pop() method on dictionaries: it removes a key and returns its value.
- The .update() method: merges two dictionaries.
- The .count() and .index() methods for tuples.
- The in operator: used with tuples to check element membership, with dictionaries to check key membership.
- Immutability of tuples: you cannot assign to an index, append, or remove elements.
- Mutability of dictionaries: you can add, modify, and delete key-value pairs.
- Iteration: for loop over a tuple yields elements; for loop over a dictionary yields keys by default.
- Unpacking a tuple: assigning each element to a separate variable in one line.

Common trap patterns:

- Forgetting the trailing comma in a single-element tuple. For example, my_tuple = (5) is actually an integer, not a tuple. The correct form is my_tuple = (5,).
- Trying to modify a tuple after creation: my_tuple[0] = 10 raises a TypeError.
- Using a mutable object (like a list) as a dictionary key. This raises a TypeError because keys must be hashable (immutable). Strings, integers, and tuples are fine; lists are not.
- Confusing the .get() method with square-bracket access. .get() returns None or a default if the key is missing; square brackets raise KeyError.
- Assuming .keys() returns a list; it returns a dict_keys view object, which is iterable but does not support list methods like .append().

Key definitions to memorise:

- Immutable: cannot be changed after creation.
- Hashable: an object that has a hash value that never changes; required for dictionary keys.
- View object: a dynamic window into a dictionary's keys, values, or items; it updates if the dictionary changes.
- Index: the position of an element in a sequence, starting at 0.
- Key: a unique identifier in a dictionary; used to retrieve the associated value.

## Step by step

1. **Create the tuple** — Decide what fixed data you need to store, such as the three primary colours. Write them inside parentheses separated by commas: primary_colours = ("red", "green", "blue"). If you have only one element, include a trailing comma: single = (42,). This distinguishes a tuple from an integer inside parentheses.
2. **Access tuple elements by index** — Use square brackets with the position number, starting at 0. For primary_colours[1], Python returns "green". Remember that using an index beyond the length causes an IndexError. You can also use negative indices to count from the end, like primary_colours[-1] for "blue".
3. **Create a dictionary** — Identify the labels (keys) and the data (values) you want to associate. Write them inside curly braces with a colon between each key and value, and commas separating pairs: student = {"name": "Bob", "age": 20, "grade": "A"}. Keys are usually strings or numbers.
4. **Access and modify dictionary values** — Retrieve a value by placing its key in square brackets: student["name"] returns "Bob". To change a value, assign to that key: student["grade"] = "B". To add a new key-value pair, simply assign to a key that does not yet exist: student["city"] = "Manchester". To remove a pair, use del student["age"] or student.pop("age").
5. **Iterate over the dictionary safely** — When you loop over a dictionary with for key in student:, the variable key takes on each key one after another. To get the value, you can use student[key] inside the loop. To get both at once, use for key, value in student.items():. This unpacking is efficient and readable.
6. **Use .get() for safe access** — If you are not certain a key exists, call student.get("height", "Not found"). If "height" is not a key, Python returns the string "Not found" instead of crashing. This is safer than using square brackets when the key may be missing.

## Comparisons

### Tuple vs List

**Tuple:**
- Created with parentheses: (1, 2, 3)
- Immutable - cannot be changed after creation
- Supports only .count() and .index() methods

**List:**
- Created with square brackets: [1, 2, 3]
- Mutable - items can be added, removed, or changed
- Supports many methods like .append(), .remove(), .sort()

### Dictionary vs Set

**Dictionary:**
- Stores key-value pairs
- Uses curly braces with colons: {"a": 1}
- Keys must be immutable and unique

**Set:**
- Stores only unique values (no keys)
- Uses curly braces without colons: {1, 2, 3}
- Elements must be immutable but no key-value pairing

### Dictionary .get() method vs Dictionary square-bracket access

**Dictionary .get() method:**
- Returns None or a default if key is missing
- Does not raise an error for missing keys
- Slower due to default value handling

**Dictionary square-bracket access:**
- Raises KeyError if key is missing
- Direct and faster when key is guaranteed to exist
- Preferred when you are certain the key is present

### Tuple Packing vs Tuple Unpacking

**Tuple Packing:**
- Combining multiple values into one tuple: a = 1, 2, 3
- Creates a tuple without parentheses
- The variable a now holds (1, 2, 3)

**Tuple Unpacking:**
- Assigning tuple elements to separate variables: x, y, z = (1, 2, 3)
- Requires the same number of variables as tuple elements
- Useful for returning multiple values from a function

## Diagram

_Decision flowchart for choosing between a tuple and a dictionary in Python, showing creation syntax and available methods._

```mermaid
flowchart TD
    A[Data Storage Need] --> B{Collection type?}
    B --> C[Ordered and fixed data]
    B --> D[Labelled and changeable data]
    C --> E[Tuple]
    D --> F[Dictionary]
    E --> G[Immutable / Use parentheses]
    F --> H[Mutable / Use curly braces]
    G --> I[Access by index]
    H --> J[Access by key]
    I --> K[.count() .index()]
    J --> L[.keys() .values() .items() .get() .pop() .update()]
```

## Common misconceptions

- **Misconception:** A tuple is just a list that you write with parentheses instead of square brackets, and you can still change it if you convert it to a list first. **Reality:** A tuple is a fundamentally different immutable data type. You cannot change a tuple's elements directly; converting to a list creates a separate object in memory. The original tuple remains unchanged. (Beginners see that both are ordered sequences and assume the only difference is syntax. They have not yet internalised the concept of immutability as a hard constraint enforced by Python.)
- **Misconception:** Dictionary keys can be any type of object, including lists. **Reality:** Dictionary keys must be immutable (hashable) objects. Lists are mutable and cannot be used as keys. Strings, integers, floats, and tuples (containing only immutable elements) are acceptable. (New learners often think that because lists are commonly used in Python, they should work anywhere. They do not realise that the requirement for keys to be hashable is a fundamental property of the dictionary's internal lookup mechanism.)
- **Misconception:** Iterating over a dictionary with a for loop gives you both the key and the value automatically. **Reality:** By default, iterating over a dictionary yields only the keys. To get both key and value, you must use the .items() method and unpack each pair: for key, value in my_dict.items(): (Python's forgiving syntax sometimes tricks beginners into assuming behaviour that is convenient rather than correct. The default iteration over keys is a design choice that confuses people who expect the most common use case to be the default.)
- **Misconception:** The .get() method on a dictionary is the same as using square brackets, just slower. **Reality:** The .get() method is different because it safely returns a default value (None by default) when the key is missing, whereas square brackets raise a KeyError. They serve distinct purposes: .get() for safe access, square brackets when you are certain the key exists. (Beginners often memorise only one way to access dictionary values and assume the alternatives are stylistic variations. They do not recognise that error handling is a critical practical difference.)
- **Misconception:** You can sort a dictionary directly using the sorted() function, and it will return a sorted dictionary. **Reality:** sorted() applied directly to a dictionary returns a sorted list of its keys. To get a sorted representation of a dictionary, you must either convert it to a list of tuples and sort that, or use the collections.OrderedDict variant (though this is beyond PCEP scope). (The word 'sorted' implies to beginners that the result will preserve the key-value pair structure. They do not yet grasp that dictionaries are inherently unordered in Python versions below 3.7 (though they are insertion-ordered in 3.7+).)
- **Misconception:** If you create a dictionary with duplicate keys, Python keeps both values and you can access them separately. **Reality:** Python does not allow duplicate keys. The last occurrence of a key overwrites the previous value. The dictionary will contain only one copy of that key with the final value assigned. (Beginners are used to lists allowing duplicates and expect the same flexibility from dictionaries. They do not understand that a key's uniqueness is essential for fast lookup.)

## Key takeaways

- A tuple is an immutable, ordered sequence defined with parentheses, and it cannot be changed after creation.
- A dictionary is a mutable, unordered collection of key-value pairs defined with curly braces.
- Dictionary keys must be immutable (hashable) types like strings, integers, or tuples of immutable objects.
- Use the .get() method on a dictionary to safely retrieve a value without risking a KeyError.
- Iterating over a dictionary with a for loop yields keys by default; use .items() to get key-value pairs.
- A single-element tuple requires a trailing comma, e.g., (5,), otherwise Python treats the parentheses as grouping operators.
- The in operator checks for key membership in a dictionary, not value membership.
- Tuples support only two methods: .count() and .index(); dictionaries support many methods including .keys(), .values(), .items(), .pop(), and .update().

## FAQ

**Why does Python have both tuples and lists if they are so similar?**

Tuples and lists serve different purposes. Tuples are immutable, meaning their contents cannot change after creation, which makes them safe to use as dictionary keys or to return multiple values from a function. Lists are mutable and are used for collections that need to grow, shrink, or be modified.

**Can I change a value inside a tuple if it contains a list?**

No, you cannot change the tuple's structure itself (e.g., replace the list with another object), but you can modify the list's contents because the list itself is mutable. The tuple holds a reference to the same list object; the reference is immutable, but the object it points to can be changed.

**What happens if I try to use a list as a dictionary key?**

Python raises a TypeError because lists are mutable and therefore not hashable. Dictionary keys must be immutable objects that have a consistent hash value. Use a tuple instead if you need a compound key.

**How do I check if a dictionary contains a specific value, not a key?**

Use value in my_dict.values(). For example, if "Alice" in my_dict.values(): checks whether the string "Alice" appears among the dictionary's values. The in operator on the dictionary itself checks only keys.

**What is the difference between .pop() and del on a dictionary?**

The .pop(key) method removes the key-value pair and returns the value that was removed. The del statement removes the pair without returning anything. Use .pop() if you need the value for further processing, otherwise del is simpler.

**Can I have a dictionary with duplicate keys?**

No, Python dictionaries enforce unique keys. If you assign a value to a key that already exists, the old value is overwritten. There is no warning; the dictionary simply keeps the latest assignment.

**What does it mean that a tuple is immutable? Can I reassign the variable to a new tuple?**

Immutability means you cannot change the elements inside the tuple after it is created. However, you can reassign the variable name to point to an entirely new tuple. For example, my_tuple = (1, 2, 3) then my_tuple = (4, 5) is allowed because you are creating a new tuple object, not modifying the first one.

## Check your understanding

1. **What is the correct way to create an empty dictionary in Python?**

   Answer: Use empty curly braces: my_dict = {}. Alternatively, the dict() constructor: my_dict = dict() creates an empty dictionary as well.

2. **How do you access the second element of a tuple named my_tuple?**

   Answer: Use my_tuple[1]. Indexing starts at 0, so the first element is at index 0, and the second element is at index 1.

3. **What does the .get() method return if the specified key does not exist in the dictionary and no default is provided?**

   Answer: It returns None. The .get() method is designed to avoid KeyError, so it returns None by default when the key is missing.

4. **Can you use a list as a key in a dictionary? Why or why not?**

   Answer: No, you cannot. Lists are mutable and therefore not hashable. Dictionary keys must be immutable objects like strings, integers, or tuples.

5. **What is the difference between iterating over a dictionary directly with a for loop versus using .items()?**

   Answer: Iterating directly yields only the keys of the dictionary. Using .items() yields key-value pairs as tuples, which can be unpacked into two variables in the loop header.

---

Interactive version with quiz and diagrams: https://courseiva.com/learn/python-pcep/tuples-and-dictionaries
