Dictionaries for Smarter Data
The Big Idea
This chapter introduces the dictionary, a powerful Python data structure that uses key-value pairs, allowing us to upgrade our inventory data from fragile, index-based lists to a robust, readable, and self-describing format.
Roadmap
The Problem with Numeric Indexes: We'll demonstrate why relying on
item[0]
for the name anditem[2]
for the price is confusing and can easily lead to bugs.Introducing Dictionaries: Learn about the dictionary data type, which stores data as
key: value
pairs, much like a real-world dictionary.Accessing Data by Key: See how
item['name']
is far more readable and safer thanitem[0]
.Refactoring
main.py
Step-by-Step: We will convert our entire application to use dictionaries instead of lists for product data.
1. **Updating the `add_product` Function:** We'll change how a `new_product` is created, moving from a list to a dictionary.
2. **Updating the `display_inventory` Function:** We'll change how we access product data for display, switching from numeric indexes to descriptive keys.
3. **Confirming `save` and `load`:** We'll see how the `json` library's power means our file-handling functions work perfectly with our new data structure without any changes.
- The Final Code: Present the fully refactored, more professional
main.py
script.
Full Chapter Content
The Problem with Remembering Numbers
Our program works. It saves, it loads, it adds, it displays. But it has a hidden weakness. Look at this line inside our display_inventory
function:
item_name, item_quantity, item_price = item
This works because we, the programmers, remember that the first item (index 0
) is the name, the second (index 1
) is the quantity, and the third (index 2
) is the price. But what if we decide to add a product SKU number? We'd have to add it to our list and then hunt through our entire program to update every single index number. If we make one mistake, we could end up trying to calculate the total value using the product's name instead of its price, causing a crash.
This is where the dictionary comes in. A dictionary doesn't use numeric positions; it uses descriptive labels.
Introducing the Python Dictionary
A dictionary is a collection that stores data in key-value pairs. It's created with curly braces {}
.
Think of it like a contact card:
Key: "Name", Value: "Alex"
Key: "Phone", Value: "555-1234"
Key: "Email", Value: "[email protected]"
Instead of asking "what's at position 0?", you ask "what's the value for the 'Name' key?".
Here's how one of our products looks as a dictionary:
{
"name": "High-Performance Laptop",
"quantity": 15,
"price": 1499.99
}
This is much clearer! The data describes itself. Now let's refactor our main.py
to use this superior structure.
Refactoring main.py
Step-by-Step
We will now modify our functions to handle a list of dictionaries instead of a list of lists.
Step 1: Update the add_product
Function
This is where a new product is born, so it's the first place we should make a change. We need to modify how the new_product
variable is created.
In your main.py
file, find the add_product
function and change only the line where new_product
is assigned.
What's New Here?
We are replacing the square brackets
[]
with curly braces{}
to create a dictionary.Each piece of data is now preceded by a key, which is a string (like
"name"
), followed by a colon:
.This makes it immediately obvious what each piece of data represents without having to guess its position.
# --- Find this function in your code ---
def add_product(inventory_list, currency_symbol):
"""Interactively asks for a new product and adds it to the list."""
print("\n--- Add a New Product ---")
product_name = input("Enter the new product name: ")
quantity_str = input(f"Enter the quantity for {product_name}: ")
price_str = input(f"Enter the price per item (in {currency_symbol}): ")
# --- THIS IS THE LINE TO CHANGE ---
# Old way: new_product = [product_name, int(quantity_str), float(price_str)]
# New, improved way using a dictionary:
new_product = {
"name": product_name,
"quantity": int(quantity_str),
"price": float(price_str)
}
inventory_list.append(new_product)
print(f"\nSUCCESS: '{product_name}' has been added to the inventory.")
Step 2: Update the display_inventory
Function
Next, we need to update how we read the data for display. Instead of relying on list unpacking or numeric indexes, we will access the values from each product dictionary using their keys.
Find the display_inventory
function and modify the for
loop inside it.
What's New Here?
Inside the loop, instead of
item_name, ... = item
, we now access each value using square brackets and the key as a string:item['name']
,item['quantity']
, anditem['price']
.This code is far more robust. If we add a new key-value pair (like a "SKU") to our product dictionaries later, this code will not break. It will still know exactly where to find the name, quantity, and price.
# --- Find this function in your code ---
def display_inventory(inventory_list, currency_symbol):
"""Displays the current inventory in a formatted table."""
print("\n--- Current Inventory Report ---")
print("-" * 60)
total_inventory_value = 0.0
if not inventory_list:
print("Inventory is currently empty.")
else:
print(f"{'Product':<30} | {'Quantity':<10} | {'Value':>15}")
print("-" * 60)
# --- THIS IS THE BLOCK TO CHANGE ---
for item in inventory_list:
# Old way: item_name, item_quantity, item_price = item
# New, improved way using dictionary keys:
item_name = item['name']
item_quantity = item['quantity']
item_price = item['price']
# The calculation and printing logic remains the same!
item_value = item_quantity * item_price
total_inventory_value += item_value
print(f"{item_name:<30} | {item_quantity:<10} | {currency_symbol}{item_value:>14.2f}")
print("-" * 60)
print(f"GRAND TOTAL INVENTORY VALUE: {currency_symbol}{total_inventory_value:>40.2f}")
print("-" * 60)
Step 3: The save
and load
Functions (No Changes Needed!)
This is the best part. How much do we need to change in our save_inventory_to_file
and load_inventory_from_file
functions? Absolutely nothing.
The json
format is so similar to Python dictionaries that the json.dump
and json.load
functions handle a list of dictionaries just as easily as they handled a list of lists. This is a huge benefit of choosing a standard format like JSON.
The Complete, Refactored main.py
Here is the entire script with our dictionary-based improvements. You can replace the whole file with this code. If you have an existing inventory.json
file from the last chapter, you should delete it, as it contains the old list-based format.
# --- PyInventory: A Step-by-Step Journey to Profit ---
# Chapter 7: Dictionaries for Smarter Data
import json
# --- Configuration ---
CURRENCY = "MAD"
DATA_FILE = "inventory.json"
# --- Function Definitions ---
def save_inventory_to_file(inventory_list, filename):
"""Saves the inventory list (of dictionaries) to a file in JSON format."""
with open(filename, 'w') as file:
json.dump(inventory_list, file, indent=4)
def load_inventory_from_file(filename):
"""Loads the inventory list (of dictionaries) from a JSON file."""
try:
with open(filename, 'r') as file:
return json.load(file)
except FileNotFoundError:
return []
def display_inventory(inventory_list, currency_symbol):
"""Displays the current inventory in a formatted table."""
print("\n--- Current Inventory Report ---")
print("-" * 60)
total_inventory_value = 0.0
if not inventory_list:
print("Inventory is currently empty.")
else:
print(f"{'Product':<30} | {'Quantity':<10} | {'Value':>15}")
print("-" * 60)
for item in inventory_list:
item_name = item['name']
item_quantity = item['quantity']
item_price = item['price']
item_value = item_quantity * item_price
total_inventory_value += item_value
print(f"{item_name:<30} | {item_quantity:<10} | {currency_symbol}{item_value:>14.2f}")
print("-" * 60)
print(f"GRAND TOTAL INVENTORY VALUE: {currency_symbol}{total_inventory_value:>40.2f}")
print("-" * 60)
def add_product(inventory_list, currency_symbol):
"""Interactively asks for a new product and adds it as a dictionary."""
print("\n--- Add a New Product ---")
product_name = input("Enter the new product name: ")
quantity_str = input(f"Enter the quantity for {product_name}: ")
price_str = input(f"Enter the price per item (in {currency_symbol}): ")
new_product = {
"name": product_name,
"quantity": int(quantity_str),
"price": float(price_str)
}
inventory_list.append(new_product)
print(f"\nSUCCESS: '{product_name}' has been added to the inventory.")
# --- Main Application Logic ---
inventory = load_inventory_from_file(DATA_FILE)
while True:
print("\n--- PyInventory Main Menu ---")
print("1: Display Current Inventory")
print("2: Add a New Product")
print("q: Quit")
choice = input("Please enter your choice (1, 2, or q): ")
if choice == '1':
display_inventory(inventory, CURRENCY)
elif choice == '2':
add_product(inventory, CURRENCY)
elif choice.lower() == 'q':
save_inventory_to_file(inventory, DATA_FILE)
print("Inventory saved. Exiting PyInventory. Goodbye!")
break
else:
print("Invalid choice. Please try again.")
Chapter 7: Summary & What's Next
You've just made your code significantly more professional, readable, and robust.
You learned that dictionaries store data using descriptive key-value pairs.
You understand why accessing data with
item['name']
is superior toitem[0]
.You successfully refactored your application to use a list of dictionaries, the standard way to handle structured data in Python.
Our application is getting more powerful. We can add items and view them, but a real inventory system needs more control. In the next chapter, we'll build on our new dictionary structure to add two essential features: updating an existing product and deleting a product from our inventory.