Python: Data Structures and Comprehensions

Once variables, conditions, loops, and functions make sense, the next step is learning how to organize data. Python's built-in collections cover most everyday jobs without needing an extra library.

Lists

A list is ordered and changeable. It is a good choice when the order matters or when items will be added and removed.

devices = ["gateway", "field node", "sensor"]

devices.append("test node")
devices[0] = "Pi gateway"

for number, device in enumerate(devices, start=1):
    print(f"{number}. {device}")

print(devices[1:3])  # A slice: items at positions 1 and 2

Tuples

A tuple is an ordered collection that should not change after it is created. Tuples are useful for fixed values such as coordinates or settings.

location = (50.4452, -104.6189)
latitude, longitude = location

print(f"Latitude: {latitude}")
print(f"Longitude: {longitude}")

Sets

A set stores unique values. It is useful for removing duplicates or testing whether a value has already been seen.

seen_nodes = {"node-a", "node-b", "node-a"}
print(seen_nodes)  # node-a appears only once

if "node-c" not in seen_nodes:
    print("This node has not reported yet")

Dictionaries

A dictionary maps keys to values. This is often the best way to represent one named object or a record with several fields.

node = {
    "name": "field-node-01",
    "battery": 87,
    "temperature": 18.4,
    "online": True,
}

print(node["name"])
print(node.get("signal", "unknown"))

for key, value in node.items():
    print(f"{key}: {value}")

List Comprehensions

A comprehension creates a collection from another iterable. Use one when the expression remains easy to read; an ordinary loop is better when the logic becomes complicated.

readings = [12.5, 18.0, 7.25, 21.5, 16.0]

# Keep only readings above 15
warm_readings = [value for value in readings if value > 15]

# Transform every value
fahrenheit = [value * 9 / 5 + 32 for value in readings]

print(warm_readings)
print(fahrenheit)

Choosing a Collection

CollectionUse it when
listYou need an ordered, changeable sequence
tupleYou need fixed grouped values
setYou need unique values or fast membership checks
dictYou need named keys mapped to values

Records and Nested Data

Real data is often a list of dictionaries: one dictionary for each node, reading, or device.

nodes = [
    {"name": "field-01", "battery": 92, "online": True},
    {"name": "field-02", "battery": 41, "online": True},
    {"name": "field-03", "battery": 8, "online": False},
]

for node in sorted(nodes, key=lambda item: item["battery"]):
    state = "online" if node["online"] else "offline"
    print(f"{node['name']}: {node['battery']}% ({state})")

low_battery = [
    node["name"] for node in nodes
    if node["battery"] < 20
]
print("Check:", ", ".join(low_battery))

Use a dictionary when a field has a name. A tuple such as (92, True) is smaller but makes the code harder to understand later.

Copies, Mutation, and Defaults

original = ["radio", "antenna"]
same_list = original       # both names point to one list
copied_list = original.copy()

same_list.append("battery")
print(original)             # changed too
print(copied_list)          # unchanged

Unexpected changes often come from two variables referring to the same mutable list or dictionary. Make an explicit copy when that is not what you want.

Practice: Summarize Readings

readings = [
    {"node": "field-01", "temperature": 18.2},
    {"node": "field-02", "temperature": 21.7},
    {"node": "field-01", "temperature": 18.9},
]

by_node = {}
for reading in readings:
    name = reading["node"]
    by_node.setdefault(name, []).append(reading["temperature"])

for name, values in by_node.items():
    average = sum(values) / len(values)
    print(f"{name}: {average:.1f} C")

References