3.4. Python Tuples: Immutable Sequences in Python#

In Python, a tuple is a versatile and fundamental data structure that resembles a list in many ways, but with a crucial distinction: tuples are immutable. This immutability provides unique advantages, making tuples useful for various scenarios where data integrity, performance, and element order preservation are paramount. Understanding how to work with tuples effectively enhances your programming skills and equips you with a valuable tool for handling collections of items that should remain unchanged. In this section, we delve into the world of tuples, exploring their characteristics, use cases, and the benefits they bring to your Python programs [Downey, 2015, Python Software Foundation, 2023].

3.4.1. Tuple Operations#

3.4.1.1. Creating a Tuple#

In Python, a tuple is an ordered, immutable collection of elements. Tuples are created using parentheses () and can contain any data type, including other tuples. Once created, the elements of a tuple cannot be modified, added, or removed. Here’s how you can create a tuple:

  1. Creating an empty tuple:

empty_tuple = ()
  1. Creating a tuple with elements:

# Tuple with integers
my_tuple = (1, 2, 3)

# Tuple with mixed data types
mixed_tuple = (1, 'hello', 3.14)

# Tuple with nested tuples
nested_tuple = ((1, 2), ('a', 'b', 'c'), (True, False))
  1. Using the tuple() constructor:

You can also create a tuple using the tuple() constructor. If you pass an iterable (like a list or another tuple) as an argument to tuple(), it will convert the iterable to a tuple.

my_list = [1, 2, 3]
tuple_from_list = tuple(my_list)  # Converts the list to a tuple

print(tuple_from_list)  # Output: (1, 2, 3)
(1, 2, 3)

Remember that tuples are immutable, so once you create a tuple, you cannot modify its elements. If you need a collection that can be modified, consider using a list instead.

3.4.1.2. Accessing Elements#

You can access individual elements of a tuple using indexing, just like lists. The index starts from 0 for the first element.

my_tuple = (10, 20, 30, 40, 50)
print(my_tuple[0])  # Output: 10
print(my_tuple[2])  # Output: 30
10
30

3.4.1.3. Tuple Slicing#

You can use slicing to extract a portion of a tuple.

my_tuple = (10, 20, 30, 40, 50)
print(my_tuple[1:4])  # Output: (20, 30, 40)
(20, 30, 40)

3.4.1.4. Tuple Length#

You can determine the length of a tuple using the len() function.

my_tuple = (10, 20, 30, 40, 50)
print(len(my_tuple))  # Output: 5
5

3.4.1.5. Immutable Nature#

Once a tuple is created, you cannot change its elements. The following code will raise an error:

my_tuple = (1, 2, 3)
my_tuple[1] = 10  # This will raise a TypeError

3.4.1.6. Tuple Packing and Unpacking#

Tuple packing is the process of assigning multiple values to a single tuple, and tuple unpacking is the opposite operation of extracting values from a tuple into variables.

# Packing
my_tuple = 10, 20, 30  # Parentheses are optional

# Unpacking
a, b, c = my_tuple
print(a, b, c)  # Output: 10 20 30
10 20 30

3.4.1.7. Single Element Tuple#

If you want to create a tuple with a single element, you need to include a comma after the element, as parentheses alone will not create a tuple with a single element.

single_element_tuple = (42,)

3.4.1.8. Tuple Addition and Multiplication#

Tuples support various operations like concatenation and repetition (using + and *, respectively).

tuple1 = (1, 2, 3)
tuple2 = (4, 5, 6)

# Concatenation
combined_tuple = tuple1 + tuple2  # Output: (1, 2, 3, 4, 5, 6)
print(combined_tuple)

# Repetition
repeated_tuple = tuple1 * 3  # Output: (1, 2, 3, 1, 2, 3, 1, 2, 3)
print(repeated_tuple)
(1, 2, 3, 4, 5, 6)
(1, 2, 3, 1, 2, 3, 1, 2, 3)

To find the maximum and minimum values of a tuple in Python, you can use the built-in max() and min() functions, respectively. These functions work with tuples just like they do with lists and other iterable objects. Here’s how you can use them:

my_tuple = (10, 30, 5, 40, 20)

# Maximum value in the tuple
max_value = max(my_tuple)
print("Maximum:", max_value)  # Output: Maximum: 40

# Minimum value in the tuple
min_value = min(my_tuple)
print("Minimum:", min_value)  # Output: Minimum: 5
Maximum: 40
Minimum: 5

3.4.1.9. Sorting Tupbles#

Returns a new sorted list (not a tuple) from the elements of the tuple.

my_tuple = (5, 3, 1, 4, 2)
sorted_list = sorted(my_tuple)
print(sorted_list)  # Output: [1, 2, 3, 4, 5]

sorted_list = sorted(my_tuple, reverse = True)
print(sorted_list)  # Output: [5, 4, 3, 2, 1]
[1, 2, 3, 4, 5]
[5, 4, 3, 2, 1]

3.4.1.10. Tuple Count Method#

The tuple count method is a fundamental operation in Python used to determine the number of occurrences of a specified element within a tuple. This method is applied to a tuple and takes one argument, which is the element you want to count. It then returns the count of that element within the tuple.

Here is the basic syntax for the tuple count method:

tuple.count(element)
  • tuple: This is the tuple on which you want to perform the counting operation.

  • element: This is the element you want to count within the tuple.

The method returns an integer representing the count of occurrences of the specified element in the tuple. If the element is not found in the tuple, it returns 0.

Example: Here’s an example of how to use the tuple count method in Python:

# Define a tuple with integer elements
my_tuple = (1, 2, 2, 3, 4, 2, 5)

# Use the tuple count method to count the occurrences of the element 2
count_of_twos = my_tuple.count(2)

# Print the result, indicating the count of twos in the tuple
print("Count of twos in the tuple:", count_of_twos)
Count of twos in the tuple: 3

In this example, the count_of_twos variable will contain the value 3, as there are three occurrences of the element 2 in the my_tuple.

3.4.2. Iterating Over a Tuple#

You can use loops to iterate over the elements of a tuple.

my_tuple = (10, 20, 30)
for element in my_tuple:
    print(element)
10
20
30

Tuples are useful when you have a collection of elements that should not change throughout the program’s execution. They are commonly used for functions that return multiple values and in situations where you want to ensure data integrity and prevent accidental modification.

3.4.3. Using the index() Method with Tuples#

The index() method allows you to locate the index (position) of the first occurrence of a specified item within a tuple. It takes the item you want to find as an argument and returns its index.

Example: Finding the index of an item in a tuple.

../_images/Tuple_Example1.png

Fig. 3.9 Visual representation of my_tuple = (10, 20, 30, 40, 50).#

my_tuple = (10, 20, 30, 40, 50)

# Find the index of the item 30 in the tuple
index = my_tuple.index(30)

# Print the result
print(f"The index of 30 is {index}")  # Output: The index of 30 is 2
The index of 30 is 2

In this example, we have a tuple my_tuple containing integers. We use the index() method to find the index of the item 30, which is at index 2 in the tuple.

Example: Handling items not found in the tuple.

../_images/Tuple_Example1.png

Fig. 3.10 Visual representation of my_tuple = (10, 20, 30, 40, 50).#

my_tuple = (10, 20, 30, 40, 50)

# Try to find the index of the item 60 in the tuple
try:
    index = my_tuple.index(60)
    print(f"The index of 60 is {index}")
except ValueError:
    print("Item not found in the tuple")

# Output: Item not found in the tuple
Item not found in the tuple

In this example, we attempt to find the index of the item 60, which is not present in the tuple. Since it’s not found, the index() method raises a ValueError exception, which we catch and handle with a message indicating that the item was not found in the tuple.

These examples illustrate how to use the index() method with tuples to find the index of a specific item and how to handle cases where the item is not present in the tuple.

3.4.4. Lists and tuples#

3.4.4.1. Using enumerate for Index-Element Pairs#

The enumerate function is another useful built-in function that returns an enumerate object. It iterates through pairs containing the index and the element from the sequence:

for index, element in enumerate('abc'):
    print(index, element)
0 a
1 b
2 c

In this code:

  1. enumerate('abc'): The enumerate() function is used to iterate through the characters of the string 'abc'. It pairs each character with its corresponding index in the string. The result of enumerate('abc') is an iterable that yields pairs of (index, element).

  2. for index, element in ...: This line initiates a for loop to iterate through the pairs produced by enumerate('abc'). In each iteration, index will represent the index of the character, and element will represent the character itself.

  3. print(index, element): Inside the loop, this line prints the index followed by the element for each character in the string. As the loop iterates, it prints both the index and the character.

3.4.4.2. Traversing Two Sequences Simultaneously#

A common use case for zip is to traverse two or more sequences simultaneously in a for loop using tuple assignment:

t = [('a', 0), ('b', 1), ('c', 2)]
for letter, number in t:
    print(number, letter)
0 a
1 b
2 c

Let’s explain the code step by step:

  1. t = [('a', 0), ('b', 1), ('c', 2)]: This line initializes a list t containing tuples. Each tuple consists of two elements: a letter (string) and a number (integer). For example, ('a', 0) pairs the letter ‘a’ with the number 0, and so on.

  2. for letter, number in t:: This line sets up a for loop to iterate through the tuples within the list t. The loop uses tuple unpacking, where each tuple is unpacked into the variables letter and number, corresponding to the elements in each tuple.

  3. print(number, letter): Inside the loop, this line prints the number followed by the letter for each tuple. As the loop iterates through t, it prints the number-letter pairs in the specified order.

3.4.4.3. zip Function in Python#

zip is a versatile built-in function in Python that takes two or more sequences, like lists, tuples, or strings, and combines them element-wise to create a zip object. The name “zip” comes from the concept of a zipper, where two rows of teeth interlock together [Downey, 2015, Python Software Foundation, 2023].

Usage:

zip_obj = zip(sequence1, sequence2, ...)

Example:

s = 'abc'
t = [0, 1, 2]

# Use the zip() function to create an iterator that combines elements from 's' and 't'
zip_result = zip(s, t)

# Print the result, which is a zip object
print(zip_result)  # Output: <zip object at 0x***********>
<zip object at 0x000002A179CFFA80>

Let’s explain the code step by step:

  1. s = 'abc': This line defines a string s containing the characters ‘a’, ‘b’, and ‘c’.

  2. t = [0, 1, 2]: Here, we create a list t containing integers 0, 1, and 2.

  3. zip_result = zip(s, t): The zip() function is used to create an iterator that combines elements from both s and t. It pairs elements at corresponding positions, resulting in an iterable zip object.

  4. print(zip_result): Finally, we print the zip_result, which is a zip object.

    • # Output: <zip object at 0x***********>: This comment indicates the expected output, showing that zip_result is a zip object located at a specific memory address.

3.4.4.4. Iterating through a Zip Object#

The most common use of zip is in a for loop to iterate through the pairs:

s = 'abc'
t = [0, 1, 2]

for pair in zip(s, t):
    print(pair)
('a', 0)
('b', 1)
('c', 2)

Let’s explain the code step by step:

  1. for pair in zip(s, t):: This line initiates a loop that iterates through pairs of elements created by the zip() function. Each pair consists of one element from the string s and one element from the list t.

  2. print(pair): Inside the loop, we print each pair of elements. This will display the combined elements from s and t at their corresponding positions.

The loop effectively combines elements from s and t and prints them one pair at a time.

3.4.4.5. Converting Zip Object to a List#

If you want to work with the elements as a list, you can convert the zip object to a list using the list() function:

zip_list = list(zip(s, t))
print(zip_list)  # Output: [('a', 0), ('b', 1), ('c', 2)]
[('a', 0), ('b', 1), ('c', 2)]

3.4.4.6. Handling Different Length Sequences#

If the input sequences are of different lengths, zip will stop creating pairs when it reaches the end of the shorter sequence:

result = list(zip('Anne', 'Elk'))
print(result)  # Output: [('A', 'E'), ('n', 'l'), ('n', 'k')]
[('A', 'E'), ('n', 'l'), ('n', 'k')]

Let’s explain the code step by step:

  1. zip('Anne', 'Elk'): In this line, the zip() function is used to combine two sequences: the string 'Anne' and the string 'Elk'. It pairs the characters at corresponding positions, creating tuples of characters. Here’s how the pairing happens:

    • First pair: (‘A’, ‘E’) - The first character of 'Anne' (‘A’) is paired with the first character of 'Elk' (‘E’).

    • Second pair: (‘n’, ‘l’) - The second character of 'Anne' (‘n’) is paired with the second character of 'Elk' (‘l’).

    • Third pair: (‘n’, ‘k’) - The third character of 'Anne' (‘n’) is paired with the third character of 'Elk' (‘k’).

  2. list(zip('Anne', 'Elk')): Here, the zip object created in the previous step is converted into a list using the list() function. This results in a list of tuples, where each tuple represents a pair of characters from the original strings.

  3. print(result): Finally, this line prints the result, which is the list of tuples created by zipping the characters from the two strings.

    • # Output: [('A', 'E'), ('n', 'l'), ('n', 'k')]: This comment indicates the expected output. It shows that the result list contains three tuples, each representing a pair of characters from the strings 'Anne' and 'Elk'.

3.4.4.7. Using zip for Matching Elements#

You can use zip to check for matching elements in two sequences:

def has_match(t1, t2):
    # Iterate through corresponding elements of t1 and t2 using zip
    for x, y in zip(t1, t2):
        if x == y:  # Check if the elements at the current positions are equal
            return True  # If a match is found, return True
    return False  # If no match is found after the loop, return False

Let’s explain the code step by step:

  1. def has_match(t1, t2):: This line defines a function called has_match that takes two sequences, t1 and t2, as input.

  2. for x, y in zip(t1, t2):: Inside the function, a for loop is used to iterate through corresponding elements of t1 and t2 using the zip() function. It pairs elements at the same positions in both sequences.

  3. if x == y:: Within the loop, this condition checks if the elements at the current positions (x from t1 and y from t2) are equal.

  4. return True: If a match is found (i.e., x and y are equal), the function immediately returns True.

  5. return False: If no match is found after the loop has checked all corresponding elements, the function returns False.

This function is designed to determine whether there is at least one matching pair of elements at the same positions in t1 and t2. If such a match exists, the function returns True; otherwise, it returns False.

3.4.5. Dictionaries and tuples#

Dictionaries in Python have a method called items() that returns a sequence of tuples, where each tuple represents a key-value pair in the dictionary. Let’s go through the examples and concepts related to dictionaries and tuples [Downey, 2015, Python Software Foundation, 2023]:

3.4.5.1. Using items() method:#

The items() method returns a dict_items object, which is an iterator over the key-value pairs of the dictionary. The order of items in the dict_items object is not guaranteed to be the same as the order of insertion in the dictionary.

3.4.5.2. Iterating through dictionary items:#

You can use the items() method in a for loop to iterate through the key-value pairs of the dictionary. In the example provided, the dictionary d contains key-value pairs ‘a’: 0, ‘b’: 1, and ‘c’: 2. When we iterate through d.items(), the output shows the key-value pairs in no particular order.

# Creating a dictionary
d = {'a': 0, 'b': 1, 'c': 2}

# Using items() to get key-value pairs as tuples
t = d.items()
print(t)  # Output: dict_items([('c', 2), ('a', 0), ('b', 1)])

# Iterating through dictionary items
for key, value in d.items():
    print(key, value)
dict_items([('a', 0), ('b', 1), ('c', 2)])
a 0
b 1
c 2

3.4.5.3. Creating a dictionary from a list of tuples:#

You can initialize a new dictionary by passing a list of tuples to the dict() function. Each tuple in the list contains a key-value pair. The example demonstrates how a list of tuples t can be used to create a new dictionary d.

# A list of tuples representing key-value pairs
t = [('a', 0), ('c', 2), ('b', 1)]

# Creating a dictionary from the list of tuples
d = dict(t)
print(d)  # Output: {'a': 0, 'c': 2, 'b': 1}
{'a': 0, 'c': 2, 'b': 1}

3.4.5.4. Creating a dictionary with zip:#

By combining dict() with zip(), you can create a dictionary in a concise manner. The zip() function combines two sequences (e.g., strings or lists) element-wise, and then dict() converts these pairs into a dictionary, with the elements from the first sequence as keys and the elements from the second sequence as values.

# Using zip to combine two sequences into pairs
keys = 'abc'
values = range(3)
d = dict(zip(keys, values))
print(d)  # Output: {'a': 0, 'c': 2, 'b': 1}
{'a': 0, 'b': 1, 'c': 2}

3.4.5.5. Using tuples as keys in dictionaries:#

Tuples can be used as keys in dictionaries, while lists cannot be used as keys because they are mutable. This is useful when you need to create a mapping between multiple values, such as in the case of a telephone directory where last-name, first-name pairs are mapped to telephone numbers.

# Telephone directory mapping last-name, first-name pairs to telephone numbers
directory = {}
directory['Doe', 'John'] = '123-456-7890'
directory['Smith', 'Alice'] = '987-654-3210'

# Accessing telephone numbers using tuple keys
print(directory['Doe', 'John'])    # Output: 123-456-7890
print(directory['Smith', 'Alice']) # Output: 987-654-3210
123-456-7890
987-654-3210

3.4.5.6. Traversing a dictionary with tuple assignment:#

Traversing a dictionary with tuple assignment allows you to access and work with the individual elements of tuple keys. This can be particularly useful when you have a dictionary with tuples as keys and you want to perform operations on each element of the tuple. Here’s an example:

# Assume we have a dictionary with tuple keys and some corresponding values
grades = {('Alice', 'Smith'): 90,
          ('Bob', 'Johnson'): 85,
          ('John', 'Doe'): 78,
          ('Emma', 'Brown'): 92
          }

# Traversing the dictionary using tuple assignment
for first, last in grades:
    print(f"{first} {last} has a grade of {grades[first, last]}")
Alice Smith has a grade of 90
Bob Johnson has a grade of 85
John Doe has a grade of 78
Emma Brown has a grade of 92

In this example, the grades dictionary has tuples containing first names and last names as keys, and integer grades as values. By using tuple assignment in the for loop, we can access each element of the tuple key (first and last) separately and use them to display the name and grade information.

Another Example:

# Assume we have a telephone directory with multiple entries
directory = {('Doe', 'John'): '123-456-7890',
             ('Smith', 'Alice'): '987-654-3210',
             ('Johnson', 'Bob'): '111-222-3333'
             }

# Traversing the dictionary with tuple assignment
for last, first in directory:
    print(first, last, directory[last, first])
John Doe 123-456-7890
Alice Smith 987-654-3210
Bob Johnson 111-222-3333

3.4.6. Sequences of sequences#

In Python, “sequences of sequences” refers to data structures where elements within the sequences are also sequences themselves. This can be achieved using different data structures, such as lists, tuples, or strings. Here, we’ll explore some examples of sequences of sequences [Downey, 2015, Python Software Foundation, 2023]:

3.4.6.1. List of Lists#

# List of lists, where each inner list represents a row in a matrix
matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

# Accessing individual elements in the matrix
print(matrix[0][0])  # Output: 1
print(matrix[1][2])  # Output: 6
1
6

3.4.6.2. List of Tuples#

# List of tuples representing points in a 2D plane
points = [(0, 0),(1, 2),(3, 5),(-1, 4)]

# Accessing individual elements in the tuples
print(points[1][0])  # Output: 1
print(points[2][1])  # Output: 5
1
5

3.4.6.3. Tuple of Lists#

# Tuple containing multiple lists
data = ([1, 2, 3], [4, 5, 6], [7, 8, 9])

# Accessing individual elements in the lists
print(data[0][1])  # Output: 2
print(data[2][0])  # Output: 7
2
7

3.4.6.4. List of Strings#

# List of strings
words = ["apple", "banana", "cherry"]

# Accessing individual characters in the strings
print(words[0][1])  # Output: 'p'
print(words[2][4])  # Output: 'r'
p
r

Sequences of sequences are widely used in various scenarios, such as representing matrices, collections of points, datasets, etc. They provide a convenient way to organize related data and enable hierarchical access to individual elements within the nested sequences.

3.4.7. Named Tuples#

Named tuples in Python are a convenient way to define lightweight classes for storing simple data structures. They provide a way to create simple classes that don’t require the full features of a custom class definition. Named tuples are essentially like regular tuples, but with named fields, making the code more readable and self-documenting.

Here’s how you can use named tuples in Python:

from collections import namedtuple

# Define a named tuple type
Person = namedtuple("Person", ["name", "age", "city"])

# Create instances of the named tuple
person1 = Person(name="Alice", age=30, city="New York")
person2 = Person(name="Bob", age=25, city="San Francisco")

# Access fields using dot notation
print(person1.name)  # Output: Alice
print(person2.age)   # Output: 25

# Named tuples are immutable
# person1.name = "Eve"  # This will raise an AttributeError
Alice
25

In the example above, we first import namedtuple from the collections module. Then, we define a named tuple type called “Person” with three fields: “name”, “age”, and “city”. We create instances of this named tuple by specifying values for each field. We can access the fields of the named tuple using dot notation, making the code more readable.

It’s important to note that named tuples are immutable, meaning you can’t modify the values of the fields after creating an instance. If you need to modify the data, you’ll need to create a new named tuple with the desired changes.

Named tuples are useful when you need a simple and readable way to represent data structures, such as when dealing with records, configurations, or other situations where you have a fixed set of fields.