Chapter 2: Core Data Structures and Comprehensions#
Every program you write spends most of its time moving data around, storing it, and reshaping it. Python gives you four flexible containers for that—tuples, lists, dictionaries, and sets—and a clean, easy-to-read way to build them called comprehensions. By the end of this chapter, you’ll be able to choose the right container naturally and build complex collections in a single line of code.
The Big Picture#
Data structures are the building blocks of any real program, whether you’re storing stock prices for a backtest, cleaning up a list of customer IDs, or mapping stocks to their risk numbers. Understanding how each one works lets you write code that’s correct, fast, and easy to read. We’ll start with the simplest one, the tuple, and build up to nested structures that look like the messy, real-world data you’ll see in finance and data science.
Tuples: Fixed Sequences#
A tuple is an ordered collection of items that cannot be changed after it is created. Think of it as a row of numbered lockers: once you put something inside, the locker stays shut. You can look at what’s in locker 0, locker 1, and so on, but you can’t swap out the contents or add a new locker.
You create a tuple by placing comma‑separated values inside parentheses, or even just by using commas without parentheses:
point = (3, 7)
date_parts = 2025, 4, 10 # parentheses are optional
single = (42,) # a one‑item tuple needs a trailing commaTuples are immutable (they can’t change). Because of that, they are hashable — they get a fixed digital fingerprint — so you can use them as dictionary keys or set elements. We’ll use that later. They’re perfect for data that shouldn’t accidentally change: a coordinate, a database row, a fixed record.
Indexing and slicing work exactly as they do with strings:
data = ("AAPL", 175.34, 1000)
ticker = data[0] # "AAPL"
price = data[1] # 175.34You can unpack a tuple into separate variables in one simple step:
ticker, price, volume = dataUnpacking is very handy when a function returns multiple values—under the hood, it’s returning a tuple.
Tuple: An ordered, immutable sequence. Once created, its elements cannot be added, removed, or changed.
📝 Section Recap: Tuples are fixed sequences that protect your data from accidental modification and enable unpacking. Use them when you have a collection of items that belong together and shouldn’t change.
Lists: Ordered, Changeable Collections#
A list is the most used container. It’s ordered, like a tuple, but you can change it after creation—add items, remove them, or reorder them. Imagine a grocery cart: you can toss things in, take them out, and rearrange them as you walk down the aisle.
Create a list with square brackets:
prices = [150.25, 152.10, 149.80]Because lists are mutable (changeable), you can use many built‑in methods to change them directly.
-
append(item) adds a single item to the end.
prices.append(151.00) # prices is now [150.25, 152.10, 149.80, 151.00] -
extend(iterable) adds every element from another iterable (like a list or tuple) to the end.
new_prices = [148.50, 149.20] prices.extend(new_prices) -
insert(index, item) slides an item into a specific position, shifting the rest to the right.
prices.insert(1, 151.55) # insert at index 1 -
remove(item) deletes the first occurrence of a value. It raises a
ValueErrorif the value isn’t found, so check first or use atryblock.prices.remove(149.80) -
pop(index) removes and returns the item at the given index (the last item if no index is supplied). This is useful for treating a list like a stack.
last_price = prices.pop() # removes and returns the last element
Lists also support slicing and indexing exactly like tuples. The big difference is that you can replace a whole slice by assigning a new list to it:
prices[0:2] = [155.00, 156.00]Because lists are mutable, they are not hashable—you cannot use a list as a dictionary key or a set element.
List: An ordered, mutable sequence. You can add, remove, and modify elements freely.
📝 Section Recap: Lists give you great flexibility for ordered data that needs to grow, shrink, or change. The methods
append,extend,insert,remove, andpopcover the vast majority of everyday list operations.
List Comprehensions#
Often you need to build a new list by transforming or filtering an existing sequence. You could write a for loop with append, but Python gives you a shorter, easier‑to‑read way: a list comprehension.
A list comprehension wraps a loop inside square brackets, producing a new list in one go. The basic syntax is:
[expression for item in iterable if condition]The if condition part is optional. Here’s a simple example that squares every number from 0 to 9:
squares = [x**2 for x in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]For a finance example, imagine you have a list of stock prices and you want to keep only those above a threshold:
prices = [145.20, 189.70, 132.50, 201.10]
high_prices = [p for p in prices if p > 150]
# [189.70, 201.10]You can nest loops inside a comprehension too, though it becomes hard to read if you nest too many loops. Suppose you want all (x, y) pairs where x and y are between 0 and 2:
pairs = [(x, y) for x in range(3) for y in range(3)]
# [(0,0), (0,1), (0,2), (1,0), ..., (2,2)]Comprehensions aren’t just a cleaner way to write loops—they are built to run faster behind the scenes. More importantly, they show your intent clearly: “I am building a new list from this data.” That clarity is invaluable when you or a colleague revisit the code months later.
List comprehension: A compact way to create a list by looping over an existing sequence, with optional filtering, all in one line.
📝 Section Recap: List comprehensions replace multi‑line loops with a single, clear line. They are both faster and more readable for generating new lists from existing sequences.
Dictionaries: Key‑Value Mappings#
A dictionary (or dict) stores pairs of keys and values. Think of it as a real‑world dictionary: you look up a word (the key) and get its definition (the value). In finance, you might map a ticker symbol to its current price, or a trade ID to a whole trade record.
Create a dictionary with curly braces, separating keys and values with colons:
portfolio = {"AAPL": 150, "GOOG": 2800, "MSFT": 310}Keys must be hashable—they need a fixed fingerprint, so strings, numbers, and tuples of hashable types work, but lists do not. Values can be anything.
Access a value by its key:
aapl_shares = portfolio["AAPL"] # 150If you try a key that doesn’t exist, Python raises a KeyError. To avoid that, use the get method, which returns None (or a value you choose) instead of crashing:
tsla_shares = portfolio.get("TSLA", 0) # 0Adding or updating an entry is as simple as assigning to a key:
portfolio["TSLA"] = 200
portfolio["AAPL"] = 175 # update existingDictionaries are mutable, so you can remove entries with del or the pop method:
del portfolio["GOOG"]
removed = portfolio.pop("MSFT") # returns 310Three dictionary methods are essential for looping:
- keys() returns a view of all keys.
- values() returns a view of all values.
- items() returns a view of (key, value) pairs.
These views stay in sync with the dictionary—if you add or remove items, the view reflects those changes. Looping over items() is the most common:
for ticker, shares in portfolio.items():
print(f"{ticker}: {shares} shares")Almost all structured data in Python—JSON objects, pandas DataFrames, config files—relies on key‑value pairs like a dictionary.
Dictionary: An unordered, mutable collection of key‑value pairs. Keys are unique and hashable; values can be anything.
📝 Section Recap: Dictionaries let you map identifiers to data, making lookups fast and intuitive.
keys(),values(), anditems()give you flexible ways to iterate over their contents.
Sets: Unique Items and Set Operations#
A set is an unordered collection of unique, hashable items. Imagine a bag of distinct marbles: no matter how many times you try to drop the same marble in, the bag only keeps one of each colour.
Create a set with curly braces (but note: empty braces {} create a dict, so for an empty set use set()):
unique_tickers = {"AAPL", "GOOG", "MSFT"}
empty = set()Sets automatically remove duplicates:
numbers = {1, 2, 2, 3, 3, 3}
# {1, 2, 3}This makes sets great for removing duplicates from a list:
trades = ["AAPL", "GOOG", "AAPL", "MSFT", "GOOG"]
unique = list(set(trades)) # order may changeBecause sets are unordered, you cannot index into them. You can test membership with in, which is extremely fast because sets use a hash table (the same fingerprint system):
"GOOG" in unique_tickers # TrueThe real power of sets comes from math‑like operations, which are very useful for comparing groups of items.
- Union (
|or.union()): all items from either set. - Intersection (
&or.intersection()): items common to both sets. - Difference (
-or.difference()): items in the first set but not the second. - Symmetric difference (
^or.symmetric_difference()): items in either set but not both.
sp500 = {"AAPL", "MSFT", "GOOG", "AMZN"}
my_portfolio = {"AAPL", "TSLA", "GOOG"}
overlap = my_portfolio & sp500 # {"AAPL", "GOOG"}
not_in_sp500 = my_portfolio - sp500 # {"TSLA"}
combined = my_portfolio | sp500 # all five tickersIn risk management, you might use set differences to find stocks that are in your portfolio but missing from a benchmark.
Set: An unordered, mutable collection of unique, hashable elements. Supports fast membership tests and operations like union and intersection.
📝 Section Recap: Sets keep only unique items and provide very fast membership checks. The union, intersection, and difference operators make comparing groups of data simple.
Nested Collections: Putting It All Together#
Real‑world data is rarely a flat list of numbers. You might have a list of trades, where each trade is a tuple of (timestamp, ticker, price, volume). Or a dictionary that maps a trader ID to a set of stocks they can trade. By combining the four types, you can model almost anything.
Consider a small order book as a list of dictionaries:
orders = [
{"id": 101, "side": "buy", "ticker": "AAPL", "price": 150.00, "volume": 100},
{"id": 102, "side": "sell", "ticker": "AAPL", "price": 151.00, "volume": 200},
{"id": 103, "side": "buy", "ticker": "GOOG", "price": 2800.0, "volume": 50},
]Now you can use comprehensions to filter and process this nested data:
# All buy orders
buys = [o for o in orders if o["side"] == "buy"]
# Total volume of AAPL orders
aapl_volume = sum(o["volume"] for o in orders if o["ticker"] == "AAPL")Notice the last example uses a generator expression inside sum()—it looks like a list comprehension without the brackets. It’s memory‑friendly because it yields items one by one instead of building a whole list.
Another common pattern is using a dictionary of lists to group items. For instance, group orders by ticker:
by_ticker = {}
for order in orders:
ticker = order["ticker"]
if ticker not in by_ticker:
by_ticker[ticker] = []
by_ticker[ticker].append(order)This can be streamlined with setdefault or, in modern Python, with defaultdict from the collections module, but the plain‑dict approach is perfectly clear and works everywhere.
When you nest mutable objects, be aware that copying a list that contains other lists with list.copy() only copies the outer list. The inner lists are still the same ones, so changing them in the copy also changes the original. This is called a shallow copy.
Nested collection: A data structure that contains other containers (lists of dicts, dicts of sets, etc.), allowing you to model complex, hierarchical data.
📝 Section Recap: By nesting tuples, lists, dictionaries, and sets, you can represent real‑world entities like order books or portfolios. Comprehensions and generator expressions extend naturally to these nested structures, keeping your code concise.
Summary#
We’ve looked at the four main Python containers: tuples for fixed records, lists for flexible sequences, dictionaries for fast lookups, and sets for unique items and comparisons. We saw how comprehensions turn loops into clean one‑liners, and how nesting these tools lets you model real‑world data like portfolios and order books. Now you know which tool to grab for each job.
| Key idea | What it means (plain English) | Why it matters |
|---|---|---|
| Tuple | An ordered, unchangeable sequence of items. | Safely groups related data; can be used as dictionary keys or set elements because it never changes. |
| List | An ordered, changeable sequence of items. | The go‑to container when you need to add, remove, or reorder elements after creation. |
| List comprehension | A single‑line expression that builds a new list from an existing sequence, with optional filtering. | Replaces multi‑line loops with faster, more readable code; clearly signals “I’m creating a new list.” |
| Dictionary | A collection of unique keys mapped to values, like a phonebook. | Provides fast lookup by key; the foundation of structured data (JSON, configuration, records). |
| Set | An unordered bag of unique items with no duplicates. | Removes duplicates instantly; supports fast membership checks and operations like union and intersection. |
| Set operations (union, intersection, difference) | Ways to combine or compare sets: union = all items, intersection = common items, difference = items in one but not the other. | Perfect for comparing groups of tickers, trade IDs, or any membership lists without writing loops. |
| Nested collection | A container (list, dict, etc.) that holds other containers inside it. | Models real‑world data like order books, portfolios, or collections of daily prices in a natural, hierarchical way. |
| Mutability | Whether an object can be changed after creation (lists, dicts, sets are mutable; tuples are not). | Determines if you can change the object later and if it can be used as a key in a dictionary or element in a set. |