Collections
Tuples
Immutable Sequences
You're storing a point (x, y) that shouldn't change. A list could be accidentally modified. A tuple is immutable - once created, it's fixed. Perfect for coordinates, RGB colors, and returning multiple values from functions.
Store coordinates
Create a tuple for a point in 2D space.
def main():
# Coordinates as tuples (x, y)
origin = (0, 0)
home = (10, 25)
office = (50, 30)
store = (35, 15)
print("=== Location Coordinates ===")
print(f"Home: {home}")
print(f"Office: {office}")
print(f"Store: {store}")
# Access individual values
home_x = home[0]
home_y = home[1]
print(f"\nHome is at x={home_x}, y={home_y}")
# 3D coordinates
point_3d = (10, 20, 30)
print(f"\n3D Point: {point_3d}")
x, y, z = point_3d
print(f"x={x}, y={y}, z={z}")
# Calculate distance from origin
import math
def distance_from_origin(point):
return math.sqrt(point[0]**2 + point[1]**2)
print("\n=== Distance from Origin ===")
locations = [("Home", home), ("Office", office), ("Store", store)]
for name, coord in locations:
dist = distance_from_origin(coord)
print(f"{name}: {dist:.2f} units")
main()
main()
36main()37#@help tupleorigin ← (0, 0), home ← (10, 25), office ← (50, 30), store ← (35, 15)
1#@var=default,3d2def main():3 # Coordinates as tuples (x, y) #?tuple4 origin→ (0, 0) = (0, 0)5 home→ (10, 25) = (10, 25)6 office→ (50, 30) = (50, 30)7 store→ (35, 15) = (35, 15)8 9 print("=== Location Coordinates ===")10 print(f"Home: {home(10, 25)}")11 print(f"Office: {office(50, 30)}")12 print(f"Store: {store(35, 15)}")13 14 # Access individual values15 home_x→ 10 = home[0]10 #?access16 home_y→ 25 = home[1]2517 print(f"\nHome is at x={home_x10}, y={home_y25}")18 19 # 3D coordinates #@var=_,!20 point_3d→ (10, 20, 30) = (10, 20, 30) #@var=_,!21 print(f"\n3D Point: {point_3d(10, 20, 30)}") #@var=_,!22 x→ 10, y→ 20, z→ 30 = point_3d(10, 20, 30) #@var=_,!23 print(f"x={x10}, y={y20}, z={z30}") #@var=_,!24 25 # Calculate distance from origin26 import math27 def distance_from_origin(point):28 return math.sqrt(point[0]**2 + point[1]**2)29 30 print("\n=== Distance from Origin ===")31 locations→ [('Home', (10, 25)), ('Office', (50, 30)), ('Store', (35, 15))] = [("Home", home(10, 25)), ("Office", office(50, 30)), ("Store", store(35, 15))]32 for name, coord in locations:output=== Location Coordinates === Home: (10, 25) Office: (50, 30) Store: (35, 15) Home is at x=10, y=25 3D Point: (10, 20, 30) x=10, y=20, z=30 === Distance from Origin ===for name, coord in locations:
pass 1 of 331locations = [("Home", home), ("Office", office), ("Store", store)]32for nameHome, coord(10, 25) in locations[('Home', (10, 25)), ('Office', (50, 30)), ('Store', (35, 15))]:33 dist = distance_from_origin(coord(10, 25))34 print(f"{name}: {dist:.2f} units")All 3 passes — pass 1 is the card above pass namecoord1 Home (10, 25) 2 Office (50, 30) 3 Store (35, 15) def distance_from_origin(point):
pass 1 of 326import math27def distance_from_origin(point(10, 25)):28 return math<module 'math' from '/usr/local/lib/python3.12/lib-dynload/math.cpython-312-x86_64-linux-gnu.so'>.sqrt(point[0]10**2 + point[1]25**2)All 3 passes — pass 1 is the card above pass pointpoint[0]point[1]1 (10, 25) 10 25 2 (50, 30) 50 30 3 (35, 15) 35 15 dist ← 26.92582403567252
32for name, coord in locations:33 dist→ 26.92582403567252 = distance_from_origin(coord(10, 25))34 print(f"{nameHome}: {dist26.92582403567252:.2f} units")outputHome: 26.93 unitsdist ← 58.309518948453004
32for name, coord in locations:33 dist→ 58.309518948453004 = distance_from_origin(coord(50, 30))34 print(f"{nameOffice}: {dist58.309518948453004:.2f} units")outputOffice: 58.31 unitsdist ← 38.07886552931954
32for name, coord in locations:33 dist→ 38.07886552931954 = distance_from_origin(coord(35, 15))34 print(f"{nameStore}: {dist38.07886552931954:.2f} units")outputStore: 38.08 unitsmain()
36main()37#@help tuple
Tuples use parentheses: (x, y). Access by index like lists.
Represent RGB colors
Store color components as a fixed triplet.
def main():
# RGB colors as tuples (red, green, blue)
# Each value: 0-255
red = (255, 0, 0)
green = (0, 255, 0)
blue = (0, 0, 255)
white = (255, 255, 255)
black = (0, 0, 0)
orange = (255, 165, 0)
print("=== RGB Color Palette ===")
colors = {
"Red": red,
"Green": green,
"Blue": blue,
"White": white,
"Black": black,
"Orange": orange
}
for name, rgb in colors.items():
print(f"{name}: RGB{rgb}")
# Color mixing (additive)
print("\n=== Color Analysis ===")
target_color = orange
r, g, b = target_color
print(f"Orange = RGB{target_color}")
print(f" Red component: {r}")
print(f" Green component: {g}")
print(f" Blue component: {b}")
# Brightness (simple average)
brightness = (r + g + b) / 3
print(f" Brightness: {brightness:.1f}/255")
# RGBA with alpha channel
transparent_red = (255, 0, 0, 128)
r, g, b, a = transparent_red
print(f"\nRGBA with alpha: {transparent_red}")
print(f" Opacity: {a/255*100:.0f}%")
main()
main()
45main()46#@help rgbred ← (255, 0, 0), green ← (0, 255, 0), blue ← (0, 0, 255), white ← (255, 255, 255)
1#@var=default,alpha2def main():3 # RGB colors as tuples (red, green, blue) #?rgb4 # Each value: 0-2555 red→ (255, 0, 0) = (255, 0, 0)6 green→ (0, 255, 0) = (0, 255, 0)7 blue→ (0, 0, 255) = (0, 0, 255)8 white→ (255, 255, 255) = (255, 255, 255)9 black→ (0, 0, 0) = (0, 0, 0)10 orange→ (255, 165, 0) = (255, 165, 0)11 12 print("=== RGB Color Palette ===")13 colors→ {'Red': (255, 0, 0), 'Green': (0, 255, 0), 'Blue': (0, 0, 255), 'White': (255, 255, 255), 'Black': (0, 0, 0), 'Orange': (255, 165, 0)} = {14 "Red": red(255, 0, 0),15 "Green": green(0, 255, 0),16 "Blue": blue(0, 0, 255),17 "White": white(255, 255, 255),18 "Black": black(0, 0, 0),19 "Orange": orange(255, 165, 0)20 }output=== RGB Color Palette ===for name, rgb in colors.items():
pass 1 of 622for nameRed, rgb(255, 0, 0) in colors{'Red': (255, 0, 0), 'Green': (0, 255, 0), 'Blue': (0, 0, 255), 'White': (255, 255, 255), 'Black': (0, 0, 0), 'Orange': (255, 165, 0)}.items():23 print(f"{nameRed}: RGB{rgb(255, 0, 0)}")outputRed: RGB(255, 0, 0)All 6 passes — pass 1 is the card above pass namergb1 Red (255, 0, 0) 2 Green (0, 255, 0) 3 Blue (0, 0, 255) 4 White (255, 255, 255) 5 Black (0, 0, 0) 6 Orange (255, 165, 0) target_color ← (255, 165, 0), r ← 255, g ← 165, b ← 0, brightness ← 140.0
25# Color mixing (additive)26print("\n=== Color Analysis ===")27target_color→ (255, 165, 0) = orange(255, 165, 0)28r→ 255, g→ 165, b→ 0 = target_color(255, 165, 0) #?unpack2930print(f"Orange = RGB{target_color(255, 165, 0)}")31print(f" Red component: {r255}")32print(f" Green component: {g165}")33print(f" Blue component: {b0}")3435# Brightness (simple average)36brightness→ 140.0 = (r255 + g165 + b0) / 337print(f" Brightness: {brightness140.0:.1f}/255")3839# RGBA with alpha channel #@var=_,!40transparent_red→ (255, 0, 0, 128) = (255, 0, 0, 128) #@var=_,!41r→ 255, g→ 0, b→ 0, a→ 128 = transparent_red(255, 0, 0, 128) #@var=_,!42print(f"\nRGBA with alpha: {transparent_red(255, 0, 0, 128)}") #@var=_,!43print(f" Opacity: {a128/255*100:.0f}%") #@var=_,!output === Color Analysis === Orange = RGB(255, 165, 0) Red component: 255 Green component: 165 Blue component: 0 Brightness: 140.0/255 RGBA with alpha: (255, 0, 0, 128) Opacity: 50%main()
45main()46#@help rgb
(red, green, blue) with values 0-255. Immutability prevents accidental changes.
Unpack tuple values
Assign tuple elements to individual variables.
def main():
# Basic tuple unpacking
coordinates = (100, 250)
x, y = coordinates
print("=== Basic Unpacking ===")
print(f"Tuple: {coordinates}")
print(f"x = {x}, y = {y}")
# Unpacking in loop
print("\n=== Unpacking in Loops ===")
points = [(0, 0), (10, 20), (30, 40), (50, 60)]
for x, y in points:
distance = (x**2 + y**2) ** 0.5
print(f"Point ({x}, {y}) is {distance:.2f} from origin")
# Swap variables using unpacking
print("\n=== Variable Swap ===")
a, b = 5, 10
print(f"Before: a={a}, b={b}")
a, b = b, a
print(f"After: a={a}, b={b}")
# Extended unpacking with * (Python 3+)
numbers = (1, 2, 3, 4, 5, 6, 7)
first, *middle, last = numbers
print("\n=== Extended Unpacking ===")
print(f"Numbers: {numbers}")
print(f"First: {first}")
print(f"Middle: {middle}")
print(f"Last: {last}")
# Ignore values with _
print("\n=== Ignoring Values ===")
data = ("John", "Doe", 30, "Engineer", "NYC")
first_name, last_name, _, job, _ = data
print(f"{first_name} {last_name} works as {job}")
main()
main()
41main()42#@help unpackcoordinates ← (100, 250), x ← 100, y ← 250, points ← [(0, 0), (10, 20), (30, 40), (50, 60)]
1#@var=default,extended2def main():3 # Basic tuple unpacking #?unpack4 coordinates→ (100, 250) = (100, 250)5 x→ 100, y→ 250 = coordinates(100, 250)6 7 print("=== Basic Unpacking ===")8 print(f"Tuple: {coordinates(100, 250)}")9 print(f"x = {x100}, y = {y250}")10 11 # Unpacking in loop12 print("\n=== Unpacking in Loops ===")13 points→ [(0, 0), (10, 20), (30, 40), (50, 60)] = [(0, 0), (10, 20), (30, 40), (50, 60)]output=== Basic Unpacking === Tuple: (100, 250) x = 100, y = 250 === Unpacking in Loops ===distance ← 0.0
pass 1 of 415for x0, y0 in points[(0, 0), (10, 20), (30, 40), (50, 60)]: #?loopunpack16 distance→ 0.0 = (x0**2 + y0**2) ** 0.517 print(f"Point ({x0}, {y0}) is {distance0.0:.2f} from origin")outputPoint (0, 0) is 0.00 from originAll 4 passes — pass 1 is the card above pass xydistance1 0 0 0.0 2 10 20 22.360679774997898 3 30 40 50.0 4 50 60 78.10249675906654 a ← 5, b ← 10, numbers ← (1, 2, 3, 4, 5, 6, 7), first ← 1, middle ← [2, 3, 4, 5, 6]
19# Swap variables using unpacking20print("\n=== Variable Swap ===")21a→ 5, b→ 10 = 5, 1022print(f"Before: a={a5}, b={b10}")23a→ 10, b→ 5 = b, a #?swap24print(f"After: a={a10}, b={b5}")2526# Extended unpacking with * (Python 3+) #?star27numbers→ (1, 2, 3, 4, 5, 6, 7) = (1, 2, 3, 4, 5, 6, 7)28first→ 1, *middle→ [2, 3, 4, 5, 6], last→ 7 = numbers(1, 2, 3, 4, 5, 6, 7) #@var=_,!29print("\n=== Extended Unpacking ===") #@var=_,!30print(f"Numbers: {numbers(1, 2, 3, 4, 5, 6, 7)}") #@var=_,!31print(f"First: {first1}") #@var=_,!32print(f"Middle: {middle[2, 3, 4, 5, 6]}") #@var=_,!33print(f"Last: {last7}") #@var=_,!3435# Ignore values with _36print("\n=== Ignoring Values ===")37data→ ('John', 'Doe', 30, 'Engineer', 'NYC') = ("John", "Doe", 30, "Engineer", "NYC")38first_name→ John, last_name→ Doe, _→ NYC, job→ Engineer, _ = data('John', 'Doe', 30, 'Engineer', 'NYC') #?ignore39print(f"{first_nameJohn} {last_nameDoe} works as {jobEngineer}")output === Variable Swap === Before: a=5, b=10 After: a=10, b=5 === Extended Unpacking === Numbers: (1, 2, 3, 4, 5, 6, 7) First: 1 Middle: [2, 3, 4, 5, 6] Last: 7 === Ignoring Values === John Doe works as Engineermain()
41main()42#@help unpack
x, y = point extracts values. Number of variables must match tuple length.
Return multiple values
Functions can return tuples for multiple return values.
def main():
# Functions can return multiple values via tuple
def get_min_max(numbers):
"""Return both minimum and maximum in one call."""
return min(numbers), max(numbers) # Returns a tuple!
scores = [85, 92, 78, 95, 88, 73, 91]
print("=== Student Scores ===")
print(f"Scores: {scores}")
# Receive both values
lowest, highest = get_min_max(scores)
print(f"Lowest: {lowest}")
print(f"Highest: {highest}")
# Extended version with more statistics
def get_stats(numbers):
"""Return comprehensive statistics."""
total = sum(numbers)
count = len(numbers)
average = total / count
minimum = min(numbers)
maximum = max(numbers)
return minimum, maximum, average, total, count
low, high, avg, total, n = get_stats(scores)
print(f"\n=== Detailed Statistics ===")
print(f"Count: {n}")
print(f"Sum: {total}")
print(f"Average: {avg:.2f}")
print(f"Range: {low} to {high}")
# Divmod - built-in that returns tuple
print("\n=== Built-in divmod() ===")
total_minutes = 137
hours, minutes = divmod(total_minutes, 60)
print(f"{total_minutes} minutes = {hours}h {minutes}m")
# Can also keep as tuple
print("\n=== Keeping as Tuple ===")
result = get_min_max(scores) # Don't unpack
print(f"Result tuple: {result}")
print(f"Type: {type(result)}")
main()
main()
48main()49#@help returnscores ← [85, 92, 78, 95, 88, 73, 91]
1#@var=default,stats2def main():3 # Functions can return multiple values via tuple #?return4 5 def get_min_max(numbers):6 """Return both minimum and maximum in one call."""7 return min(numbers), max(numbers) # Returns a tuple!8 9 scores→ [85, 92, 78, 95, 88, 73, 91] = [85, 92, 78, 95, 88, 73, 91]10 11 print("=== Student Scores ===")12 print(f"Scores: {scores[85, 92, 78, 95, 88, 73, 91]}")13 14 # Receive both values15 lowest, highest = get_min_max(scores[85, 92, 78, 95, 88, 73, 91]) #?receive16 print(f"Lowest: {lowest}")output=== Student Scores === Scores: [85, 92, 78, 95, 88, 73, 91]def get_min_max(numbers):
pass 1 of 25def get_min_max(numbers[85, 92, 78, 95, 88, 73, 91]):6 """Return both minimum and maximum in one call."""7 return min(numbers[85, 92, 78, 95, 88, 73, 91]), max(numbers) # Returns a tuple!lowest ← 73, highest ← 95
14# Receive both values15lowest→ 73, highest→ 95 = get_min_max(scores[85, 92, 78, 95, 88, 73, 91]) #?receive16print(f"Lowest: {lowest73}")17print(f"Highest: {highest95}")1819# Extended version with more statistics #@var=_,!20def get_stats(numbers): #@var=_,!21 """Return comprehensive statistics.""" #@var=_,!22 total = sum(numbers) #@var=_,!23 count = len(numbers) #@var=_,!24 average = total / count #@var=_,!25 minimum = min(numbers) #@var=_,!26 maximum = max(numbers) #@var=_,!27 return minimum, maximum, average, total, count #@var=_,!2829low, high, avg, total, n = get_stats(scores[85, 92, 78, 95, 88, 73, 91]) #@var=_,!30print(f"\n=== Detailed Statistics ===") #@var=_,!outputLowest: 73 Highest: 95total ← 602, count ← 7, average ← 86.0, minimum ← 73, maximum ← 95
19# Extended version with more statistics #@var=_,!20def get_stats(numbers[85, 92, 78, 95, 88, 73, 91]): #@var=_,!21 """Return comprehensive statistics.""" #@var=_,!22 total→ 602 = sum(numbers[85, 92, 78, 95, 88, 73, 91]) #@var=_,!23 count→ 7 = len(numbers[85, 92, 78, 95, 88, 73, 91]) #@var=_,!24 average→ 86.0 = total602 / count7 #@var=_,!25 minimum→ 73 = min(numbers[85, 92, 78, 95, 88, 73, 91]) #@var=_,!26 maximum→ 95 = max(numbers[85, 92, 78, 95, 88, 73, 91]) #@var=_,!27 return minimum73, maximum95, average86.0, total602, count7 #@var=_,!low ← 73, high ← 95, avg ← 86.0, total ← 602, n ← 7, total_minutes ← 137
29low→ 73, high→ 95, avg→ 86.0, total→ 602, n→ 7 = get_stats(scores[85, 92, 78, 95, 88, 73, 91]) #@var=_,!30print(f"\n=== Detailed Statistics ===") #@var=_,!31print(f"Count: {n7}") #@var=_,!32print(f"Sum: {total602}") #@var=_,!33print(f"Average: {avg86.0:.2f}") #@var=_,!34print(f"Range: {low73} to {high95}") #@var=_,!3536# Divmod - built-in that returns tuple37print("\n=== Built-in divmod() ===")38total_minutes→ 137 = 13739hours→ 2, minutes→ 17 = divmod(total_minutes137, 60) #?divmod40print(f"{total_minutes137} minutes = {hours2}h {minutes17}m")4142# Can also keep as tuple43print("\n=== Keeping as Tuple ===")44result = get_min_max(scores[85, 92, 78, 95, 88, 73, 91]) # Don't unpack45print(f"Result tuple: {result}")output === Detailed Statistics === Count: 7 Sum: 602 Average: 86.00 Range: 73 to 95 === Built-in divmod() === 137 minutes = 2h 17m === Keeping as Tuple ===def get_min_max(numbers):
pass 2 of 25def get_min_max(numbers[85, 92, 78, 95, 88, 73, 91]):6 """Return both minimum and maximum in one call."""7 return min(numbers[85, 92, 78, 95, 88, 73, 91]), max(numbers) # Returns a tuple!result ← (73, 95)
43print("\n=== Keeping as Tuple ===")44result→ (73, 95) = get_min_max(scores[85, 92, 78, 95, 88, 73, 91]) # Don't unpack45print(f"Result tuple: {result(73, 95)}")46print(f"Type: {type(result(73, 95))}")outputResult tuple: (73, 95) Type: <class 'tuple'>main()
48main()49#@help return
return x, y implicitly creates a tuple. Caller can unpack or use as tuple.
Named tuples for clarity
Use named tuples for self-documenting fields.
from collections import namedtuple
def main():
# Regular tuple - position-based access
person_tuple = ("Alice", 30, "Engineer")
print("=== Regular Tuple ===")
print(f"Data: {person_tuple}")
print(f"Name: {person_tuple[0]}") # What is [0]? Hard to remember!
print(f"Age: {person_tuple[1]}")
# Named tuple - name-based access!
Person = namedtuple('Person', ['name', 'age', 'job'])
alice = Person("Alice", 30, "Engineer")
bob = Person(name="Bob", age=25, job="Designer")
print("\n=== Named Tuple ===")
print(f"Alice: {alice}")
print(f"Name: {alice.name}") # Much clearer!
print(f"Age: {alice.age}")
print(f"Job: {alice.job}")
# Still works with index too
print(f"Index access still works: {alice[0]}")
# Unpacking works
name, age, job = bob
print(f"\nUnpacked Bob: {name}, {age}, {job}")
# Named tuple for coordinates
Point = namedtuple('Point', ['x', 'y'])
print("\n=== Points as Named Tuples ===")
origin = Point(0, 0)
destination = Point(100, 50)
dx = destination.x - origin.x
dy = destination.y - origin.y
distance = (dx**2 + dy**2) ** 0.5
print(f"From: {origin}")
print(f"To: {destination}")
print(f"Distance: {distance:.2f}")
# Create from existing tuple
data = (200, 300)
point = Point._make(data)
print(f"\nCreated from tuple: {point}")
# Convert to dictionary
print(f"As dict: {point._asdict()}")
main()
main()
53main()54#@help regularperson_tuple ← ('Alice', 30, 'Engineer'), Person ← <class '__main__.Person'>
3def main():4 # Regular tuple - position-based access #?regular5 person_tuple→ ('Alice', 30, 'Engineer') = ("Alice", 30, "Engineer")6 print("=== Regular Tuple ===")7 print(f"Data: {person_tuple('Alice', 30, 'Engineer')}")8 print(f"Name: {person_tuple[0]Alice}") # What is [0]? Hard to remember!9 print(f"Age: {person_tuple[1]30}")10 11 # Named tuple - name-based access! #?named12 Person→ <class '__main__.Person'> = namedtuple('Person', ['name', 'age', 'job'])13 14 alice→ Person(name='Alice', age=30, job='Engineer') = Person("Alice", 30, "Engineer")15 bob→ Person(name='Bob', age=25, job='Designer') = Person(name="Bob", age=25, job="Designer")16 17 print("\n=== Named Tuple ===")18 print(f"Alice: {alicePerson(name='Alice', age=30, job='Engineer')}")19 print(f"Name: {alice.nameAlice}") # Much clearer!20 print(f"Age: {alice.age30}")21 print(f"Job: {alice.jobEngineer}")22 23 # Still works with index too24 print(f"Index access still works: {alice[0]Alice}")25 26 # Unpacking works27 name→ Bob, age→ 25, job→ Designer = bobPerson(name='Bob', age=25, job='Designer')28 print(f"\nUnpacked Bob: {nameBob}, {age25}, {jobDesigner}")29 30 # Named tuple for coordinates31 Point→ <class '__main__.Point'> = namedtuple('Point', ['x', 'y'])32 33 print("\n=== Points as Named Tuples ===")34 origin→ Point(x=0, y=0) = Point(0, 0)35 destination→ Point(x=100, y=50) = Point(100, 50)36 37 dx→ 100 = destination.x100 - origin.x038 dy→ 50 = destination.y50 - origin.y039 distance→ 111.80339887498948 = (dx100**2 + dy50**2) ** 0.540 41 print(f"From: {originPoint(x=0, y=0)}")42 print(f"To: {destinationPoint(x=100, y=50)}")43 print(f"Distance: {distance111.80339887498948:.2f}")44 45 # Create from existing tuple46 data→ (200, 300) = (200, 300)47 point→ Point(x=200, y=300) = Point<class '__main__.Point'>._make(data(200, 300)) #?make48 print(f"\nCreated from tuple: {pointPoint(x=200, y=300)}")49 50 # Convert to dictionary51 print(f"As dict: {pointPoint(x=200, y=300)._asdict()}")output=== Regular Tuple === Data: ('Alice', 30, 'Engineer') Name: Alice Age: 30 === Named Tuple === Alice: Person(name='Alice', age=30, job='Engineer') Name: Alice Age: 30 Job: Engineer Index access still works: Alice Unpacked Bob: Bob, 25, Designer === Points as Named Tuples === From: Point(x=0, y=0) To: Point(x=100, y=50) Distance: 111.80 Created from tuple: Point(x=200, y=300) As dict: {'x': 200, 'y': 300}main()
53main()54#@help regular
namedtuple gives names to positions: point.x instead of point[0].
Exercise: tuple_vs_list.py
Explore when to choose tuple vs list