3  Compound Objects

In our exploration of Python so far, we’ve worked with fundamental objects that represent single pieces of information: numbers for quantities, strings for text, and booleans for true/false values. However, real-world computational problems often require us to work with multiple related values as a coherent unit.

Consider these examples from different domains. In geometry, a point is defined by its coordinates (x,y). In graphics, a color is specified by its RGB components (red, green, blue). In databases, a record might contain (ID, name, date). In physics, a particle’s state includes (position, velocity, mass).

Each of these concepts is naturally represented as a collection of related values that together form a meaningful unit. We need a way to bundle multiple values together so we can:

  1. Treat the collection as a single object
  2. Ensure the values stay together in a meaningful way
  3. Pass multiple values as a single unit to functions
  4. Return multiple values from functions

Python provides several ways to create such compound objects, each designed for different use cases. We’ll start with the simplest and most fundamental: tuples.

3.1 Tuples

A tuple is Python’s most basic compound object - a fixed sequence of values that, once created, cannot be changed. This immutability (unchangeability) is a key feature that makes tuples perfect for representing collections of values that logically belong together and shouldn’t change.

Think of a tuple like a sealed package:

  • Once you put items in and seal it, the contents are fixed
  • You can look inside to see what’s there
  • You can pass the whole package around
  • But you can’t add, remove, or swap items

This immutability isn’t a limitation - it’s a powerful feature that helps us:

  1. Express intent - using a tuple says “these values belong together and won’t change”
  2. Prevent bugs - accidental modifications are impossible
  3. Enable optimizations - Python can optimize operations on immutable objects
  4. Build more complex structures - tuples can be used as dictionary keys or set elements

Common uses for tuples include:

  • Geometric points: (x, y) or (x, y, z)
  • Color values: (red, green, blue)
  • Database records: (id, name, email)
  • Date/time components: (year, month, day)
  • Function return values: (result, error_code)

The fixed nature of tuples reflects the fact that many concepts in computing naturally have a fixed structure - a 2D point always has exactly two coordinates, an RGB color always has exactly three components, etc.

3.1.1 Working with Tuples

Let’s explore how to create and use tuples in practice. Remember that tuples represent fixed collections of values that logically belong together.

3.1.1.1 Creating Tuples

The most straightforward way to create a tuple is to separate values with commas, optionally enclosed in parentheses:

# Basic tuple creation
coordinates = (3, 4)        # A 2D point
rgb_color = (255, 128, 0)   # An RGB color value

# Parentheses are optional but recommended for clarity
point = 3, 4               # Also creates a tuple

For single-element tuples, you must include a comma after the value:

single = (42,)    # Creates a one-element tuple
not_tuple = (42)  # Just a number in parentheses!

This comma requirement might seem strange, but it helps Python distinguish between parentheses used for grouping expressions and those used for creating tuples.

3.1.1.2 Accessing Tuple Elements

Since tuples represent fixed collections of values, each position in a tuple has a specific meaning. You access tuple elements using index numbers in square brackets, starting from 0:

# Consider a point in 3D space
point = (10, 20, 30)

x = point[0]    # x coordinate: 10
y = point[1]    # y coordinate: 20
z = point[2]    # z coordinate: 30

You can also use negative indices to count from the end:

rgb = (255, 128, 0)
blue = rgb[-1]   # Last value: 0
green = rgb[-2]  # Second-to-last: 128

3.1.1.3 Using Tuples Effectively

Tuples are ideal for representing:

  1. Coordinates and geometric points:
point2d = (3, 4)
point3d = (3, 4, 5)
  1. Fixed collections of related values:
rgb_black = (0, 0, 0)
rgb_white = (255, 255, 255)
  1. Database records or rows:
record = ("John", "Doe", "1990-01-01")

3.1.1.4 Tuple Unpacking

One of tuple’s most useful features is unpacking, where you assign tuple elements to variables in one step:

# Basic unpacking
point = (3, 4)
x, y = point              # x = 3, y = 4

# Unpacking with multiple values
person = ("John", 30, "New York")
name, age, city = person

# Using * to collect remaining values
numbers = (1, 2, 3, 4, 5)
first, second, *rest = numbers
# first = 1, second = 2, rest = [3, 4, 5]

# Ignoring values with _
data = (10, 20, 30, 40)
x, _, y, _ = data        # Ignore second and fourth values

3.1.2 Tuple Methods

Since tuples are immutable, they have only two methods:

numbers = (1, 2, 2, 3, 2, 4)

# count() - count occurrences of an element
count_2 = numbers.count(2)    # Result: 3

# index() - find position of first occurrence
pos_2 = numbers.index(2)      # Result: 1

3.1.3 Getting Tuple Length

Like other sequences, len() gives the number of elements:

# Basic length examples
point = (10, 20)
length = len(point)       # Result: 2

empty = ()
length = len(empty)       # Result: 0

3.2 Lists

While tuples provide a way to group related values into an immutable collection, many programming tasks require collections that can be modified after creation. For example, we might need to:

  • Add or remove items from a shopping cart during an online purchase
  • Update a list of high scores in a game as players achieve better results
  • Maintain a growing collection of data points in a scientific experiment
  • Track a changing set of active users in a system

For these scenarios, Python provides lists - mutable sequences that can grow or shrink as needed. Like tuples, lists can hold any type of data (numbers, text, or even other lists), but unlike tuples, their contents can be modified after creation.

3.2.1 Creating Lists

The simplest way to create a list is to use square brackets [] with items separated by commas:

# A list of numbers
numbers = [1, 2, 3, 4, 5]

# A list of strings
fruits = ["apple", "banana", "orange"]

# A mixed list (can contain different types)
mixed = [1, "hello", 3.14, True]

You can also create an empty list in two ways:

empty_list1 = []           # Using square brackets
empty_list2 = list()       # Using the list() function

3.2.2 Indexing Lists

In Python, you access list items using their index (position) in the list. Important things to remember: - Indexing starts at 0, not 1 - Negative indices count from the end (-1 is the last item)

colors = ["red", "green", "blue", "yellow"]

# Positive indexing (from start)
first_color = colors[0]    # "red"
second_color = colors[1]   # "green"

# Negative indexing (from end)
last_color = colors[-1]    # "yellow"
second_last = colors[-2]   # "blue"

Here’s a visual to help understand indexing:

List:  ["red",   "green",   "blue",   "yellow"]
Index:    0         1         2          3
Neg:     -4        -3        -2         -1

3.2.3 Slicing Lists

Slicing lets you extract multiple items from a list using the format list[start:end]. The end index is not included in the slice.

numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# Basic slicing
first_three = numbers[0:3]     # [0, 1, 2]
middle = numbers[3:7]          # [3, 4, 5, 6]

# Shortcuts
from_start = numbers[:4]       # [0, 1, 2, 3]
until_end = numbers[7:]        # [7, 8, 9]
whole_list = numbers[:]        # Makes a copy of the entire list

You can also use a step value in slices with the format list[start:end:step]:

numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# Every second number
every_second = numbers[::2]    # [0, 2, 4, 6, 8]

# Every third number starting from index 1
custom_slice = numbers[1:8:3]  # [1, 4, 7]

# Reverse the list
reversed_list = numbers[::-1]  # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

3.2.4 Modifying Lists

Lists are mutable, which means you can change their contents after creation:

# Changing individual elements
fruits = ["apple", "banana", "orange"]
fruits[1] = "grape"           # Now: ["apple", "grape", "orange"]

# Slice assignment
numbers = [0, 1, 2, 3, 4]
numbers[1:4] = [10, 20, 30]   # Replace multiple elements at once
                              # Now: [0, 10, 20, 30, 4]

# You can even replace a slice with a different number of elements
numbers[1:4] = [50]           # Now: [0, 50, 4]

3.2.5 Adding to Lists

There are several ways to add items to a list:

fruits = ["apple", "banana"]

# Add one item to the end
fruits.append("orange")        # Now: ["apple", "banana", "orange"]

# Add multiple items to the end
fruits.extend(["grape", "kiwi"])
# Now: ["apple", "banana", "orange", "grape", "kiwi"]

# Insert at a specific position
fruits.insert(1, "mango")     # Insert at index 1
# Now: ["apple", "mango", "banana", "orange", "grape", "kiwi"]

3.2.6 Getting List Length

The len() function is used to find out how many items are in a list. It’s a built-in Python function that works with many types of objects, but with lists it simply counts the number of elements:

# Basic length examples
numbers = [1, 2, 3, 4, 5]
length = len(numbers)    # Result: 5

empty_list = []
length = len(empty_list)  # Result: 0

mixed = [1, "hello", True, 3.14]
length = len(mixed)     # Result: 4

The len() function is particularly useful when you need to:

  1. Check if a list is empty (length is 0)
  2. Process the last few elements of a list
  3. Iterate through list indices
  4. Verify the size of a list before performing operations

3.2.7 List Comprehensions

List comprehensions provide a concise way to create lists based on existing lists or other sequences. They’re like a compact for-loop that creates a new list. Let’s break down the syntax step by step.

3.2.7.1 Basic Syntax

The basic format is:

[expression for item in sequence]

This consists of three parts:

  1. expression: What to put in the new list - this is where you use the item variable to specify what you want to do with each element
  2. item: A variable name you choose that will represent each element as Python goes through the sequence - this name is then used in the expression to say what to do with each element
  3. sequence: The list or other sequence you’re iterating over

The item variable is like a temporary name that holds each element from your sequence one at a time, so you can use that name in your expression to say what you want to do with it. For example:

# In [x**2 for x in range(5)]:
# - x is the item variable (could be any name)
# - range(5) is the sequence [0,1,2,3,4]
# - x**2 is the expression that uses x to square each number
squares = [x**2 for x in range(5)]
# Python goes through each number in range(5)
# puts it in x, then puts x**2 in the new list
# Result: [0, 1, 4, 9, 16]

3.2.7.2 Adding Conditions

You can add an if condition to filter items:

[expression for item in sequence if condition]

The parts are:

  1. expression: What to put in the new list
  2. item: Variable for each element
  3. sequence: What you’re iterating over
  4. if condition: Only include items that make this condition True

Example with filtering:

# Only include even numbers
numbers = [1, 2, 3, 4, 5, 6]
evens = [x for x in numbers if x % 2 == 0]
# Result: [2, 4, 6]

# Same as this traditional for loop:
evens = []
for x in numbers:
    if x % 2 == 0:
        evens.append(x)

3.2.7.3 Real-World Examples

Temperature conversion:

# Convert Celsius temperatures to Fahrenheit
celsius = [0, 10, 20, 30]
fahrenheit = [c * 9/5 + 32 for c in celsius]
# Result: [32.0, 50.0, 68.0, 86.0]

Working with strings:

# Get lengths of words
words = ["hello", "world", "python"]
lengths = [len(word) for word in words]
# Result: [5, 5, 6]

# Convert words to uppercase
upper_words = [word.upper() for word in words]
# Result: ["HELLO", "WORLD", "PYTHON"]

Filtering with conditions:

# Get words longer than 4 letters
words = ["cat", "dog", "elephant", "bird", "giraffe"]
long_words = [word for word in words if len(word) > 4]
# Result: ["elephant", "giraffe"]

# Square numbers greater than 2
squares = [x**2 for x in range(6) if x > 2]
# Result: [9, 16, 25]

Remember:

  • List comprehensions make code shorter but shouldn’t be too complex
  • If your list comprehension is hard to read, it’s better to use a regular for loop
  • They’re great for simple transformations and filtering

3.3 Dictionaries

Dictionaries are another fundamental data structure in Python. Think of a dictionary like a real-world dictionary where you look up words (keys) to find their definitions (values). In Python, dictionaries let you store and retrieve values using unique keys, making them perfect for organizing related data.

3.3.1 Creating Dictionaries

The simplest way to create a dictionary is to use curly braces {} with key-value pairs separated by commas:

# A simple dictionary
person = {"name": "John", "age": 30, "city": "New York"}

# A dictionary with different value types
mixed = {
    "number": 42,
    "text": "hello",
    "list": [1, 2, 3],
    "bool": True
}

You can also create an empty dictionary in two ways:

empty_dict1 = {}            # Using curly braces
empty_dict2 = dict()        # Using the dict() function

3.3.2 Accessing Values

You access dictionary values using their keys in square brackets or with the get() method:

person = {"name": "John", "age": 30, "city": "New York"}

# Using square brackets
name = person["name"]      # "John"
age = person["age"]        # 30

# Using get() method (safer - returns None if key doesn't exist)
city = person.get("city")  # "New York"
country = person.get("country")  # None
# You can specify a default value
country = person.get("country", "Unknown")  # "Unknown"

3.3.3 Modifying Dictionaries

Dictionaries are mutable, so you can change, add, or remove key-value pairs:

person = {"name": "John", "age": 30}

# Changing existing values
person["age"] = 31         # Update age

# Adding new key-value pairs
person["city"] = "Boston"  # Add new pair

# Updating multiple pairs at once
person.update({
    "age": 32,
    "job": "Engineer",
    "city": "Seattle"
})

# Removing items
removed_age = person.pop("age")     # Removes and returns the value
del person["city"]                  # Removes the key-value pair

3.3.4 Dictionary Methods

Common dictionary methods for working with keys and values:

person = {
    "name": "John",
    "age": 30,
    "city": "New York"
}

# Get all keys
keys = person.keys()       # dict_keys(['name', 'age', 'city'])

# Get all values
values = person.values()   # dict_values(['John', 30, 'New York'])

# Get all key-value pairs as tuples
items = person.items()     # dict_items([('name', 'John'), ('age', 30), ('city', 'New York')])

# Check if key exists
has_name = "name" in person              # True
has_country = "country" in person        # False

3.3.5 Getting Dictionary Length

Like lists, the len() function counts the number of key-value pairs in a dictionary:

# Basic length examples
person = {"name": "John", "age": 30, "city": "New York"}
length = len(person)    # Result: 3

empty_dict = {}
length = len(empty_dict)  # Result: 0

3.3.6 Dictionary Comprehensions

Similar to list comprehensions, dictionary comprehensions provide a concise way to create dictionaries:

# Basic syntax:
# {key_expression: value_expression for item in sequence}

# Create a dictionary of number squares
squares = {x: x**2 for x in range(5)}
# Result: {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# Create from two lists
keys = ["a", "b", "c"]
values = [1, 2, 3]
dict_from_lists = {k: v for k, v in zip(keys, values)}
# Result: {"a": 1, "b": 2, "c": 3}

# With conditions
even_squares = {x: x**2 for x in range(6) if x % 2 == 0}
# Result: {0: 0, 2: 4, 4: 16}

3.3.7 Real-World Examples

Here are some practical examples of using dictionaries:

# Student grades
grades = {
    "Alice": {"math": 90, "science": 95, "history": 88},
    "Bob": {"math": 85, "science": 92, "history": 94},
    "Charlie": {"math": 88, "science": 87, "history": 90}
}

# Calculate average grade for each student
averages = {
    student: sum(scores.values()) / len(scores)
    for student, scores in grades.items()
}

# Configuration settings
config = {
    "database": {
        "host": "localhost",
        "port": 5432,
        "name": "myapp"
    },
    "api": {
        "url": "https://api.example.com",
        "key": "abc123",
        "timeout": 30
    }
}

Remember:

  • Dictionary keys must be immutable (strings, numbers, tuples)
  • Each key must be unique within the dictionary
  • Values can be of any type, including other dictionaries
  • Dictionaries are perfect for structured data with named fields

3.4 Sets

Sets are unordered collections of unique elements in Python. Think of a set like a bag that can hold items, but each item can only appear once, and the order doesn’t matter. Sets are perfect for removing duplicates from data and performing mathematical set operations like unions and intersections.

3.4.1 Creating Sets

You can create a set using curly braces {} (like dictionaries, but without key-value pairs) or the set() function:

# Creating sets with curly braces
fruits = {"apple", "banana", "orange"}

# Creating a set from a list (removes duplicates)
numbers = set([1, 2, 2, 3, 3, 4])  # Result: {1, 2, 3, 4}

# Mixed types are allowed
mixed = {42, "hello", True, 3.14}

You can create an empty set using the set() function (empty curly braces {} create an empty dictionary):

empty_set = set()          # Create empty set

3.4.2 Set Operations

Sets support mathematical set operations like union, intersection, and difference:

set1 = {1, 2, 3, 4}
set2 = {3, 4, 5, 6}

# Union (all elements from both sets)
union = set1 | set2               # {1, 2, 3, 4, 5, 6}
# Alternative: set1.union(set2)

# Intersection (elements common to both sets)
intersection = set1 & set2        # {3, 4}
# Alternative: set1.intersection(set2)

# Difference (elements in set1 but not in set2)
difference = set1 - set2          # {1, 2}
# Alternative: set1.difference(set2)

# Symmetric difference (elements in either set, but not both)
sym_diff = set1 ^ set2           # {1, 2, 5, 6}
# Alternative: set1.symmetric_difference(set2)

3.4.3 Modifying Sets

Sets are mutable, so you can add or remove elements:

colors = {"red", "green", "blue"}

# Add single element
colors.add("yellow")       # Now: {"red", "green", "blue", "yellow"}

# Add multiple elements
colors.update(["orange", "purple"])
# Now: {"red", "green", "blue", "yellow", "orange", "purple"}

# Remove element (raises KeyError if not found)
colors.remove("blue")

# Remove element (no error if not found)
colors.discard("black")    # No error if "black" isn't in the set

# Remove and return an arbitrary element
popped = colors.pop()

# Remove all elements
colors.clear()             # Now: set()

3.4.4 Set Methods

Common set methods for checking membership and relationships:

set1 = {1, 2, 3, 4}
set2 = {3, 4}
set3 = {5, 6}

# Check if element exists
has_one = 1 in set1               # True
has_five = 5 in set1              # False

# Check if one set is subset of another
is_subset = set2 <= set1          # True
# Alternative: set2.issubset(set1)

# Check if one set is superset of another
is_superset = set1 >= set2        # True
# Alternative: set1.issuperset(set2)

# Check if sets are disjoint (no common elements)
are_disjoint = set1.isdisjoint(set3)  # True

3.4.5 Getting Set Length

The len() function counts the number of unique elements in a set:

# Basic length examples
numbers = {1, 2, 3, 4, 5}
length = len(numbers)      # Result: 5

empty_set = set()
length = len(empty_set)    # Result: 0

# Creating set from list with duplicates
items = set([1, 2, 2, 3, 3, 3])
length = len(items)        # Result: 3 (unique elements only)

3.4.6 Set Comprehensions

Like lists and dictionaries, sets support comprehensions for creating sets concisely:

# Basic syntax:
# {expression for item in sequence}

# Create a set of squares
squares = {x**2 for x in range(5)}
# Result: {0, 1, 4, 9, 16}

# With conditions
even_squares = {x**2 for x in range(6) if x % 2 == 0}
# Result: {0, 4, 16}

# Create set from string characters
vowels = {char for char in "hello world" if char in 'aeiou'}
# Result: {'e', 'o'}

3.4.7 Real-World Examples

Here are some practical examples of using sets:

# Remove duplicates from a list
numbers = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
unique_numbers = list(set(numbers))
# Result: [1, 2, 3, 4]

# Find unique words in text
text = "the quick brown fox jumps over the lazy dog"
unique_words = set(text.split())
# Result: {'the', 'quick', 'brown', 'fox', 'jumps', 'over', 'lazy', 'dog'}

# Find common tags between posts
post1_tags = {"python", "programming", "tutorial"}
post2_tags = {"python", "beginner", "tutorial"}
common_tags = post1_tags & post2_tags
# Result: {"python", "tutorial"}

# Check valid moves in a game
valid_moves = {"up", "down", "left", "right"}
def is_valid_move(move):
    return move.lower() in valid_moves

Remember:

  • Sets are unordered - don’t rely on element order
  • Sets can only contain immutable elements (no lists or dictionaries)
  • Sets are perfect for removing duplicates
  • Set operations are very efficient for large collections
  • Use sets when order doesn’t matter and uniqueness does

3.5 Exercises

  1. List Operations

    Given this list:

    fruits = ["apple", "banana", "orange"]

    What will be printed by:

    1. print(fruits[0])
    2. print(fruits[-1])
    3. print(fruits[1:])

    Explain why each result occurs.

  2. Tuple Unpacking

    Given these tuples:

    point = (3, 4)
    person = ("Alice", 25, "New York")

    Write code to:

    1. Unpack point into variables x and y
    2. Unpack person into variables name, age, and city
    3. Print formatted strings using the unpacked variables
  3. Dictionary Methods

    Given this dictionary:

    student = {
        "name": "Bob",
        "age": 20,
        "grade": "A"
    }

    How would you:

    1. Get the value of “name”
    2. Safely get a non-existent key “score” with a default value
    3. Get all keys in the dictionary
  4. Set Operations

    Create two sets:

    set1 = {1, 2, 3}
    set2 = {3, 4, 5}

    Calculate and explain:

    1. The union of set1 and set2
    2. The intersection of set1 and set2
    3. The difference between set1 and set2
  5. Nested Data Structures

    Given this nested structure:

    data = {
        "numbers": [1, 2, 3],
        "point": (4, 5),
        "valid": True
    }

    How would you access:

    1. The first number in the “numbers” list
    2. The second value in the “point” tuple
    3. The boolean value
  6. List to Dictionary Conversion

    Given these lists:

    names = ["Alice", "Bob", "Charlie"]
    ages = [25, 30, 35]

    Write code to create a dictionary where names are keys and ages are values.

  7. Set and String Operations

    Given:

    vowels = {"a", "e", "i", "o", "u"}
    text = "hello"

    Write code to find which vowels appear in the text.

  8. Dictionary Manipulation

    Starting with:

    profile = {"name": "Alice", "age": 25}

    Write code to:

    1. Add a new key “city” with value “Boston”
    2. Update multiple values using the update() method
    3. Print the modified dictionary