🗃️ Containers & Pythonic#

Open In Colab

Goal: Strengthen understanding of Python’s built‑in containers and idioms.
Time: 50 minutes
Prereqs: Variables, loops, basic functions. No external packages required.

Agenda#

  • Built‑in containers: lists, tuples, sets, dicts

  • Advanced usage & idioms

  • Mini‑project (Grades and Sensor tracks included)

  • Wrap‑up & resources

1. Built‑in Containers#

Lists#

Lists are mutable ordered sequences of objects, created with square brackets. Common ops: append, extend, insert, pop, sort.

nums = [10, 30, 20]
nums.append(40)
nums[1] = 25
print(nums)

Lists are indexed by integers, starting with zero. Use the indexing operator to access and modify individual items of the list:

names = ["Dave", "Mark", "Ann", "Phil"]
names[0] = "Jeff"
names.append("Paula")
names.insert(2, "Thomas")
print(names)

Lists can be sliced and concatenated using:

x[start:stop:stride]
mixed = [0, 1, 2, 'e', 3, 'pi']
print(mixed)
print(mixed[0:2])
print(mixed[:2])
print(mixed[2:5])
print(mixed[2:])
print(mixed[-2:])
mixed[5] = 'pi'
print(mixed)
mixed.extend([4,5])
print(mixed)
mixed.append([6,7,8])
print(mixed)

Use the plus + operator to concatenate lists:

a = [1,2,3]+[4,5,6]
print(a)

An empty list can be created by one of two ways

names = []
print(names)
names = list()
print(names)

Lists can contain any kind of Python object, including other lists, as in the following example:

a = [1, "Dave", 3.14, ["Mark", 7, 9, [0, 101]], 10]
print(a)

Items contained in nested lists are accessed by applying more than one indexing operation, as follows

print(a[1])
print(a[3])
print(a[3][1])
print(a[3][3][1])

Tuples#

Tuples are immutable ordered sequences of objects. These simple data structures are great for fixed‑size records. You can create a tuple by enclosing group of values in parentheses like this:

weekdays = ("Mon", "Tue", "Wed", "Thu", "Fri")

Python often recognizes that a tuple is intended even if the parentheses are missing

weekdays = "Mon", "Tue", "Wed", "Thu", "Fri"
print(weekdays)
point = (3.0, 4.0)
print(point, "length:", len(point))

For completeness, 0- and 1-element tuples can be defined, but have special syntax:

a = ()    # 0-tuple, empty tuple
b = (1,)  # 1-tuple  
c = 1,    # 1-tuple
print(type(a))
print(type(b))
print(type(c))
b = (1)   # not a tuple - IMPORTANT
c = 1     # not a tuple
print(type(b))
print(type(c))

The values in a tuple can be extracted by numerical index just like a list. However, it is more common to unpack tuples into a set of variables like this

stock = "GOOG", 100, 490.10
name, shares = stock[0:2]
print(name, shares)
name, shares, _ = stock
print(name, shares)

Although tuples support most of the same operations as lists, such as indexing, slicing, and concatenation, the contents of a tuple cannot be modified after creation. That is, you cannot replace, delete, or append new elements to an exisiting tuple.

print(stock[1]) 
stock[1] = 200  # error

Note: Python tuples and strings are immutable. This means that once it is created, its contents cannot be changed — elements cannot be added, removed, or altered. On the other hand, Python lists and sets are mutable.

Sets#

A set in Python is an unordered, mutable collection that stores unique elements. It allows for fast membership testing, making it efficient to check whether an item exists in the set. Sets also support mathematical operations such as union (|), intersection (&), and difference (-).

You can create a set using curly braces {} or the set() constructor:

fruits = {"apple", "banana", "cherry"}
print(fruits)

colors = set(["red", "green", "blue"])
print(colors)

Sets automatically eliminate duplicate values:

nums = {1, 2, 2, 3, 4, 4, 5}
print(nums)  # Output: {1, 2, 3, 4, 5}

Sets are unordered, so they do not support indexing or slicing:

print(fruits[0])  # Error: 'set' object is not subscriptable

You can add elements to a set using the .add() method:

fruits.add("orange")
print(fruits)

Sets support fast membership testing using the in keyword:

print("apple" in fruits)
print("grape" in fruits)

Sets support powerful mathematical operations:

# Union (`|`) combines elements from both sets:
a = {1, 2, 3}
b = {3, 4, 5}
print(a | b)  # Output: {1, 2, 3, 4, 5}

# Intersection (`&`) returns common elements:
print(a & b)  # Output: {3}

# Difference** (`-`) returns elements in one set but not the other:

print(a - b)  # Output: {1, 2}

You can also use .union(), .intersection(), and .difference() methods:

print(a.union(b))
print(a.intersection(b))
print(a.difference(b))

Note: Sets are ideal for storing unique items and performing membership tests or set algebra. Unlike lists and tuples, sets do not maintain order and do not support indexing. They are mutable, but their elements must be immutable types (e.g., numbers, strings, tuples).

Dictionaries#

A dictionary is a mutable, unordered collection of key-value pairs. You can create one using curly braces {} or the dict() constructor:

person = {"name": "Alice", "age": 30}
print(person)
empty_dict = dict()

Use square brackets to access values by key, and assign new values or add new pairs:

print(person["name"])
person["age"] = 31
person["email"] = "alice@example.com"
print(person)

The .get() method lets you access values without raising an error if the key is missing:

print(person.get("phone"))        # None
print(person.get("phone", "N/A")) # N/A

Use .pop() or del to remove key-value pairs:

person.pop("email")
del person["age"]
print(person)

You can iterate over keys, values, or both:

person["age"] = 31
person["email"] = "alice@example.com"
for key, value in person.items():
    print(f"{key}: {value}")

Note: Dictionaries are perfect for storing structured data. Keys must be immutable and unique, while values can be of any type. Unlike lists, dictionaries use keys—not indices—to access data.

Strings#

A string is a immutable sequence of characters enclosed in single, double, or triple quotes:

greeting = "Hello"
print(greeting)
name = 'Alice'
print(name)
message = """This is a multi-line string."""
print(message)

The same type of quote used to start a string must be used to terminate it. Triple-quoted strings capture all the text that appears prior to the terminating triple quote, as opposed to single- and double-quoted strings, which must be specified on one logical line. Triple-quoted strings are useful when the contents of a string literal span multiple lines of text such as the following:

print('''Content-type: text/html

<h1> Hello World </h1>
Click <a href="http://www.python.org">here</a>.
''')

Strings are indexed like lists—use square brackets to access individual characters:

print(greeting[0])   # Output: H
print(name[-1])      # Output: e

Use + to join strings and * to repeat them:

full = greeting + ", " + name + "!"
print(full)  # 'Hello, Alice!'
echo = "Hi! " * 3
print(echo)  # 'Hi! Hi! Hi! '

Strings can be sliced using the same syntax as lists:

word = "Python"
print(word[0:3])   # 'Pyt'
print(word[::2])   # 'Pto'

Strings are immutable, meaning their contents cannot be changed after creation:

text = "hello"
# text[0] = "H"  # Error: strings can't be modified
new_text = "H" + text[1:]
print(new_text)  # 'Hello'

Python offers multiple ways to format strings. Here are some common methods:

title = "The legendary pirate captain"
age = 726.25

# method 1: (formatted-string)%(values)
print("%s is %f years old."%(title, age))     # print a string and a floating point
print("%s is %.4f years old."%(title, age))   # 4 decimal places
print("%s is %10.4f years old."%(title, age)) # allocate 10 spaces
print("%s is %d years old."%(title, age))     # print it as integer
print("%s is %10d years old."%(title, age))   # allocate 10 spaces

# method 2: (formatted-string).format(values)
print("{} is {} years old.".format(title, age)) 
print("{} is {:0.4f} years old.".format(title, age))
print("{} is {:10.4f} years old.".format(title, age))

# method 3: f(formatted-string with values)
print(f"{title} is {age} years old.")
print(f"{title} is {age:.4f} years old.")
print(f"{title} is {age:10.4f} years old.")

note: Strings are one of the most commonly used data types in Python. They are immutable, support indexing and slicing, and come with a rich set of methods for formatting, searching, and transforming text.

Exercise 1#

Pick the right container for each scenario and write a one‑liner creating it:

  1. unique student IDs from [101,102,101,103]

  2. 3D position (x,y,z) from floats

  3. gradebook mapping names to grades for Ana=92, Bo=90, Cat=88

  4. recent sensor window for last 5 readings 1..5

Type your answers in the next cell.

# Your answers here
unique_ids = ...
position = ...
gradebook = ...
window = ...

# Quick checks (uncomment when you've written your answers)
# assert unique_ids == {101,102,103}
# assert isinstance(position, tuple) and len(position) == 3
# assert gradebook == {"Ana":92, "Bo":90, "Cat":88}
# assert window == [1,2,3,4,5]

2. Pythonic Coding#

"Pythonic" refers to writing code in a way that is idiomatic and aligns with the principles and style of the Python programming language. It emphasizes readability, simplicity, and elegance, often leveraging Python’s unique features and conventions.

Comprehensions#

Using list, set, and dictionary comprehensions to write concise and expressive code.

squares = [n*n for n in range(10)]
evens   = {n for n in range(20) if n % 2 == 0}
freqs   = {c: ord(c) for c in "ACE"}
print(squares)
print(evens)
print(freqs)

Comparing Pythonic vs Non-Pythonic List Construction#

The following Pythonic version is faster because list comprehensions are optimized internally in C, reducing overhead from repeated method calls like .append() in loops. It also avoids the need to grow the list dynamically, which improves memory efficiency.

# Non-pythonic: the list grows dynamically
squares = []
for i in range(10):
    squares.append(i * i)
print(squares)

# Pythonic: Concise and much faster
squares = []
squares = [i * i for i in range(10)]
print(squares)

The expression:

squares = [i * i for i in range(10)]

does not dynamically grow the list during execution like .append() does. Instead, Python’s list comprehension:

  1. Pre-allocates memory for the entire list because the size is known in advance (range(10) has 10 elements).

  2. Executes in C-level bytecode, which is much faster than repeatedly calling Python-level methods like .append().

This means the list is created in one go, avoiding the overhead of resizing and method calls, which is why it’s both faster and more memory-efficient.

Simplifying Conditional Statements#

Using Python’s expressive syntax to make conditional logic cleaner and more readable.

The followng code checks each condition separately, which works but is verbose and harder to maintain.

# hand = input("What would you like to play? ")
hand = 'Shears'
if hand == "Rock":
    print("It is a valid play.")
elif hand == "Paper":
    print("It is a valid play.")
elif hand == "Scissors":
    print("It is a valid play.")
else:    
    print("It is an invalid play.")

Combining conditions with or reduces repetition and improves clarity.

# hand = input("What would you like to play? ")
hand = 'Shears'
if hand == "Rock" or hand == "Paper" or hand == "Scissors":
    print("It is a valid play")
else:    
    print("It is an invalid play")

The most Pythonic approach uses in to check if a value exists in a tuple, making the code concise and elegant.

# hand = input("What would you like to play? ")  # This is Pythonic
hand = 'Shears'
if hand in  ("Rock", "Paper", "Scissors"):  
    print("It is a valid play")
else:    
    print("It is an invalid play")    

Ternary Operator#

Python’s ternary operator provides a compact way to write simple conditional assignments.

Sandard if-else Assignment: This version uses a full if-else block to assign a value based on a condition.

y = 1

if y == 1:
    x = 1
else: 
    x = 0
print(x)

Pythonic Ternary Expression: This version condenses the logic into a single line, improving readability and conciseness.

x = 1 if y == 1 else 0  # This is Pythonic
print(x)

Loops#

Exploring Pythonic ways to iterate over sequences with index tracking.

Manual Indexing with a Loop: This approach manually tracks the index using a separate variable, which works but is more error-prone and verbose.

# indexing using for loops
names = ['Peter Parker', 'Clark Kent', 'Wade Wilson', 'Bruce Wayne', 'Dr. Baek']
index = 0
for name in names:
    print(index, name)
    index += 1

Using enumerate() for Cleaner Looping: enumerate() simplifies index tracking by automatically pairing each item with its index.

names = ['Peter Parker', 'Clark Kent', 'Wade Wilson', 'Bruce Wayne', 'Dr. Baek']
for index, name in enumerate(names):
    print(index, name)

Starting Index from a Custom Value: You can customize the starting index with enumerate(..., start=1) for more control over output formatting.

names = ['Peter Parker', 'Clark Kent', 'Wade Wilson', 'Bruce Wayne', 'Dr. Baek']
for index, name in enumerate(names, start=1):
    print(index, name)

Iterating Through Multiple Lists#

Python provides elegant ways to iterate over multiple sequences in parallel.

Manual Indexing with enumerate(): This approach uses enumerate() to access both the index and the value, allowing you to manually retrieve corresponding elements from another list.

heroes = ['Spiderman', 'Superman', 'Deadpool', 'Batman', 'Pirate Captain']
for index, name in enumerate(names):
    hero = heroes[index]
    print(f'{name} is actually {hero}')

Using zip() for Parallel Iteration: zip() pairs elements from multiple lists, making the code cleaner and more readable when iterating through them together.

for name, hero in zip(names, heroes):
    print(f'{name} is actually {hero}')

Extending zip() to More Than Two Lists: You can use zip() with three or more lists to combine related data from multiple sources in a single loop.

universes = ['Marvel', 'DC', 'Marvel', 'DC', 'USAFA']
for name, hero, universe in zip(names, heroes, universes):
    print(f'{name} is actually {hero} from {universe}')

3. Wrap‑up#

Takeaways

  • Choose the right container: list/tuple/set/dict.

  • Prefer comprehensions, enumerate, zip, and key= sorts for readability.

Further reading