Python Programming Fundamentals: A Complete Beginner’s Guide (Part 2)



This content originally appeared on DEV Community and was authored by Fonyuy Gita

Welcome back to our comprehensive Python programming series! In Part 1, we covered the fundamentals of programming, variables, strings, conditionals, and loops. Now we’re ready to explore more powerful concepts that will transform you from writing simple scripts to building organized, reusable programs.

Table of Contents

  1. Quick Review of Part 1
  2. Functions: Building Your Own Tools
  3. Understanding Scope: Where Variables Live
  4. Data Structures: Organizing Your Data
    • Lists: Your Digital Shopping Cart
    • Dictionaries: Your Digital Address Book
    • Tuples: Unchangeable Data Containers
    • Sets: Collections of Unique Items
  5. Working with Multiple Data Structures
  6. What’s Next in Part 3

Quick Review of Part 1

Before we dive into new concepts, let’s quickly review what we learned in Part 1. Think of these as the basic tools in your programming toolbox:

Variables – Your labeled storage boxes that hold different types of data

name = "Alice"
age = 25
is_student = True

Strings – Text manipulation and formatting

message = f"Hello, {name}! You are {age} years old."

Conditionals – Decision-making in your programs

if age >= 18:
    print("You can vote!")
else:
    print("Too young to vote.")

Loops – Repeating actions efficiently

for i in range(5):
    print(f"Count: {i}")

These fundamentals are like learning the alphabet before writing sentences. Now we’re ready to start writing the equivalent of paragraphs and essays in Python!

Functions: Building Your Own Tools

What are Functions?

Imagine you’re a chef in a restaurant. Instead of writing out the complete recipe for making scrambled eggs every time you need them, you create a standard procedure called “make_scrambled_eggs.” Whenever you need scrambled eggs, you just follow that procedure. Functions in programming work exactly the same way.

A function is a reusable block of code that performs a specific task. Think of functions as custom tools that you create to solve specific problems. Just like a can opener is designed specifically to open cans, a function is designed to perform one particular job.

Why Use Functions?

Consider this scenario: You’re writing a program that needs to calculate the area of a circle in multiple places. Without functions, you might write:

# Without functions - repetitive and error-prone
radius1 = 5
area1 = 3.14159 * radius1 * radius1
print(f"Area of circle 1: {area1}")

radius2 = 10
area2 = 3.14159 * radius2 * radius2
print(f"Area of circle 2: {area2}")

radius3 = 7
area3 = 3.14159 * radius3 * radius3
print(f"Area of circle 3: {area3}")

This is like writing the same recipe three times instead of creating one recipe you can use repeatedly. With functions, we can do this:

# With functions - clean, reusable, and maintainable
def calculate_circle_area(radius):
    """Calculate the area of a circle given its radius."""
    pi = 3.14159
    area = pi * radius * radius
    return area

# Now we can use our function multiple times
area1 = calculate_circle_area(5)
area2 = calculate_circle_area(10)
area3 = calculate_circle_area(7)

print(f"Area of circle 1: {area1}")
print(f"Area of circle 2: {area2}")
print(f"Area of circle 3: {area3}")

Creating Your First Function

Let’s break down the anatomy of a function using a simple greeting example:

def greet_person(name):
    """This function greets a person by name."""
    message = f"Hello, {name}! Welcome to Python programming!"
    return message

# Using the function
greeting = greet_person("Alice")
print(greeting)

Let’s examine each part:

Function Definition (def): This is like putting a label on your tool box. The def keyword tells Python “I’m about to create a new function.”

Function Name (greet_person): This is the name you’ll use to call your function later. Choose descriptive names that clearly indicate what the function does.

Parameters (name): These are like the ingredients you need to give your function. They’re the inputs your function needs to do its job.

Docstring ("""This function..."""): This is like an instruction manual for your function. It explains what the function does and how to use it.

Function Body: This is where the actual work happens. All the code that’s indented under the def line is part of the function.

Return Statement: This is like the output tray of your function. It sends the result back to whoever called the function.

Functions with Multiple Parameters

Functions can accept multiple inputs, just like a blender can take multiple ingredients:

def calculate_rectangle_area(length, width):
    """Calculate the area of a rectangle."""
    area = length * width
    return area

def introduce_person(first_name, last_name, age, city):
    """Create a personal introduction."""
    introduction = f"Hi, I'm {first_name} {last_name}. I'm {age} years old and I live in {city}."
    return introduction

# Using functions with multiple parameters
rect_area = calculate_rectangle_area(10, 5)
intro = introduce_person("John", "Doe", 30, "New York")

print(f"Rectangle area: {rect_area}")
print(intro)

Functions with Default Parameters

Sometimes you want to make certain parameters optional, like having a default setting on a machine:

def greet_with_title(name, title="Friend"):
    """Greet someone with an optional title."""
    return f"Hello, {title} {name}!"

# Using the function with and without the optional parameter
print(greet_with_title("Alice"))                # Uses default title
print(greet_with_title("Bob", "Dr."))          # Uses custom title
print(greet_with_title("Charlie", "Professor")) # Uses custom title

Functions That Don’t Return Values

Not all functions need to return something. Some functions just perform actions, like a light switch that just turns lights on or off:

def print_separator():
    """Print a decorative separator line."""
    print("=" * 50)

def countdown(start_number):
    """Count down from a given number."""
    for i in range(start_number, 0, -1):
        print(f"Countdown: {i}")
    print("Blast off! 🚀")

# Using functions that don't return values
print_separator()
print("Welcome to the Launch Sequence")
print_separator()
countdown(5)

Function Examples and Practice

Let’s look at some practical examples that demonstrate the power of functions:

# Temperature converter function
def celsius_to_fahrenheit(celsius):
    """Convert Celsius to Fahrenheit."""
    fahrenheit = (celsius * 9/5) + 32
    return fahrenheit

def fahrenheit_to_celsius(fahrenheit):
    """Convert Fahrenheit to Celsius."""
    celsius = (fahrenheit - 32) * 5/9
    return celsius

# Password strength checker
def check_password_strength(password):
    """Check if a password meets basic security requirements."""
    # Initialize strength indicators
    has_upper = False
    has_lower = False
    has_digit = False
    has_special = False

    # Check each character in the password
    for char in password:
        if char.isupper():
            has_upper = True
        elif char.islower():
            has_lower = True
        elif char.isdigit():
            has_digit = True
        elif char in "!@#$%^&*()":
            has_special = True

    # Determine strength level
    if len(password) >= 8 and has_upper and has_lower and has_digit and has_special:
        return "Strong"
    elif len(password) >= 6 and has_upper and has_lower and has_digit:
        return "Medium"
    else:
        return "Weak"

# Simple calculator functions
def add(a, b):
    """Add two numbers."""
    return a + b

def subtract(a, b):
    """Subtract second number from first."""
    return a - b

def multiply(a, b):
    """Multiply two numbers."""
    return a * b

def divide(a, b):
    """Divide first number by second."""
    if b != 0:
        return a / b
    else:
        return "Error: Cannot divide by zero!"

# Using our functions
print(f"25°C = {celsius_to_fahrenheit(25)}°F")
print(f"77°F = {fahrenheit_to_celsius(77):.1f}°C")

password_strength = check_password_strength("MyPassword123!")
print(f"Password strength: {password_strength}")

print(f"5 + 3 = {add(5, 3)}")
print(f"10 / 2 = {divide(10, 2)}")
print(f"10 / 0 = {divide(10, 0)}")

Function Exercises

Exercise 1: Personal Information Function

def create_profile(name, age, occupation, city):
    """Create a personal profile string."""
    # Your code here
    # Return a formatted string with all the information
    pass

# Test your function
profile = create_profile("Alice", 28, "Engineer", "San Francisco")
print(profile)

Exercise 2: Grade Calculator Function

def calculate_grade(score):
    """Convert numerical score to letter grade."""
    # Your code here
    # Return appropriate letter grade (A, B, C, D, F)
    # Include + and - modifiers for scores ending in 7-9 and 0-2
    pass

# Test your function
print(calculate_grade(95))  # Should return "A"
print(calculate_grade(87))  # Should return "B+"
print(calculate_grade(83))  # Should return "B-"

Exercise 3: Shopping Cart Total Function

def calculate_total(price, quantity, tax_rate=0.08):
    """Calculate total cost including tax."""
    # Your code here
    # Calculate subtotal, tax amount, and final total
    # Return the final total
    pass

# Test your function
total = calculate_total(29.99, 3)
print(f"Total cost: ${total:.2f}")

Understanding Scope: Where Variables Live

What is Scope?

Imagine your house has different rooms, and each room has its own set of items. You can’t use the kitchen blender in the bedroom because it belongs to the kitchen. Similarly, variables in Python have “scope” – they belong to specific parts of your program.

Scope determines where in your program a variable can be accessed. Think of it as the “living space” of your variables.

Local Scope vs Global Scope

# Global variable - lives in the "main house"
house_name = "Python Manor"

def cook_dinner():
    # Local variable - lives only in this "kitchen"
    recipe = "Spaghetti Carbonara"
    cooking_time = 30

    # This function can access both local and global variables
    print(f"Cooking {recipe} in {house_name}")
    print(f"Estimated cooking time: {cooking_time} minutes")

    return recipe

def set_table():
    # Different local scope - this is like a "dining room"
    plates = 4
    utensils = "fork, knife, spoon"

    # Can access global variable
    print(f"Setting table for dinner at {house_name}")
    print(f"Using {plates} plates with {utensils}")

    # Cannot access recipe or cooking_time from cook_dinner function
    # This would cause an error: print(recipe)

# Using the functions
dinner = cook_dinner()
set_table()

# Can access global variable
print(f"Welcome to {house_name}!")

# Cannot access local variables from outside their functions
# This would cause an error: print(recipe)

Modifying Global Variables

Sometimes you need to change a global variable from inside a function. This is like asking permission to rearrange furniture in the main house from your bedroom:

# Global counter
total_visitors = 0

def welcome_visitor(name):
    """Welcome a visitor and update the counter."""
    global total_visitors  # Ask permission to modify global variable
    total_visitors += 1
    print(f"Welcome, {name}! You are visitor #{total_visitors}")

def show_visitor_count():
    """Display current visitor count."""
    print(f"Total visitors today: {total_visitors}")

# Using the functions
welcome_visitor("Alice")
welcome_visitor("Bob")
welcome_visitor("Charlie")
show_visitor_count()

Data Structures: Organizing Your Data

So far, we’ve been working with individual pieces of data like single numbers or strings. But in real programming, you often need to work with collections of related data. This is where data structures come in – they’re like different types of containers for organizing your information.

Think of data structures as different ways to organize items in your house. You might use a bookshelf for books, a jewelry box for jewelry, or a filing cabinet for documents. Each container is designed for a specific purpose and makes it easy to find and manage your items.

Lists: Your Digital Shopping Cart

What are Lists?

A list is like a shopping cart that can hold multiple items in a specific order. You can add items, remove items, change items, and look at what’s in your cart at any time. Lists are one of the most versatile and commonly used data structures in Python.

# Creating lists - like filling up your shopping cart
shopping_cart = ["apples", "bread", "milk", "eggs"]
numbers = [1, 2, 3, 4, 5]
mixed_items = ["Alice", 25, True, 3.14]  # Lists can hold different types
empty_cart = []  # An empty list, ready to be filled

print(f"Shopping cart: {shopping_cart}")
print(f"Numbers: {numbers}")
print(f"Mixed items: {mixed_items}")

Accessing List Items

Lists are like a row of numbered boxes. You can access any item by its position (called an “index”). Python starts counting from 0, not 1:

fruits = ["apple", "banana", "cherry", "date", "elderberry"]

# Accessing items by index (position)
print(f"First fruit: {fruits[0]}")      # apple
print(f"Second fruit: {fruits[1]}")     # banana
print(f"Last fruit: {fruits[-1]}")      # elderberry (negative indexing)
print(f"Second to last: {fruits[-2]}")  # date

# Getting slices (portions) of the list
print(f"First three fruits: {fruits[0:3]}")    # ['apple', 'banana', 'cherry']
print(f"From second to end: {fruits[1:]}")     # ['banana', 'cherry', 'date', 'elderberry']
print(f"Every second fruit: {fruits[::2]}")    # ['apple', 'cherry', 'elderberry']

Modifying Lists

Unlike your shopping list written in pen, Python lists are written in pencil – you can change them:

# Starting with a basic grocery list
groceries = ["milk", "bread", "eggs"]
print(f"Original list: {groceries}")

# Adding single items (like adding items to your cart)
groceries.append("cheese")
print(f"After adding cheese: {groceries}")

# Adding multiple items at once
groceries.extend(["apples", "bananas"])
print(f"After adding fruits: {groceries}")

# Inserting an item at a specific position
groceries.insert(1, "yogurt")  # Insert at index 1
print(f"After inserting yogurt: {groceries}")

# Changing an existing item
groceries[0] = "almond milk"  # Change milk to almond milk
print(f"After changing milk: {groceries}")

# Removing items
groceries.remove("bread")  # Remove by value
print(f"After removing bread: {groceries}")

removed_item = groceries.pop()  # Remove and return last item
print(f"Removed item: {removed_item}")
print(f"After popping: {groceries}")

specific_item = groceries.pop(1)  # Remove and return item at index 1
print(f"Removed item at index 1: {specific_item}")
print(f"Final list: {groceries}")

List Methods and Operations

Lists come with many built-in methods, like different features on a Swiss Army knife:

# Sample list of test scores
test_scores = [85, 92, 78, 96, 88, 79, 94]

# Getting information about the list
print(f"Number of scores: {len(test_scores)}")
print(f"Highest score: {max(test_scores)}")
print(f"Lowest score: {min(test_scores)}")
print(f"Average score: {sum(test_scores) / len(test_scores):.1f}")

# Checking if items exist
print(f"Is 85 in the list? {85 in test_scores}")
print(f"Is 100 in the list? {100 in test_scores}")

# Counting occurrences
grades = ["A", "B", "A", "C", "B", "A", "D"]
print(f"Number of A's: {grades.count('A')}")

# Finding the position of an item
print(f"Position of first 'B': {grades.index('B')}")

# Sorting (this changes the original list)
test_scores.sort()
print(f"Sorted scores: {test_scores}")

# Sorting in descending order
test_scores.sort(reverse=True)
print(f"Scores (highest to lowest): {test_scores}")

# Reversing the list
test_scores.reverse()
print(f"Reversed list: {test_scores}")

Working with Lists in Loops

Lists and loops work together like peanut butter and jelly. Here are common patterns:

# List of students
students = ["Alice", "Bob", "Charlie", "Diana", "Eve"]

# Simple iteration
print("Class roster:")
for student in students:
    print(f"- {student}")

# Iteration with index numbers
print("\nNumbered class roster:")
for i, student in enumerate(students):
    print(f"{i + 1}. {student}")

# Processing each item
prices = [19.99, 25.50, 12.75, 33.20, 8.99]
print("\nPrice list with tax:")
for price in prices:
    price_with_tax = price * 1.08
    print(f"${price:.2f} -> ${price_with_tax:.2f}")

# Creating new lists based on existing ones
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_numbers = []
for num in numbers:
    if num % 2 == 0:
        even_numbers.append(num)

print(f"Even numbers: {even_numbers}")

# List comprehension - a compact way to create lists
squares = [x**2 for x in range(1, 11)]
print(f"Squares: {squares}")

even_squares = [x**2 for x in range(1, 11) if x % 2 == 0]
print(f"Even squares: {even_squares}")

Practical List Examples

# Todo list manager
todo_list = []

def add_task(task):
    """Add a task to the todo list."""
    todo_list.append(task)
    print(f"Added: '{task}'")

def complete_task(task):
    """Mark a task as completed by removing it."""
    if task in todo_list:
        todo_list.remove(task)
        print(f"Completed: '{task}'")
    else:
        print(f"Task '{task}' not found in the list")

def show_tasks():
    """Display all pending tasks."""
    if todo_list:
        print("Your tasks:")
        for i, task in enumerate(todo_list, 1):
            print(f"{i}. {task}")
    else:
        print("No tasks pending. Great job!")

# Using the todo list
add_task("Buy groceries")
add_task("Write Python code")
add_task("Call mom")
show_tasks()

complete_task("Buy groceries")
show_tasks()

# Grade tracker
def calculate_class_statistics(grades):
    """Calculate statistics for a class."""
    if not grades:
        return "No grades to analyze"

    total = sum(grades)
    count = len(grades)
    average = total / count

    # Count letter grades
    letter_grades = []
    for grade in grades:
        if grade >= 90:
            letter_grades.append("A")
        elif grade >= 80:
            letter_grades.append("B")
        elif grade >= 70:
            letter_grades.append("C")
        elif grade >= 60:
            letter_grades.append("D")
        else:
            letter_grades.append("F")

    return {
        "average": average,
        "highest": max(grades),
        "lowest": min(grades),
        "total_students": count,
        "letter_grades": letter_grades
    }

# Example usage
class_grades = [85, 92, 78, 96, 88, 79, 94, 87, 91, 83]
stats = calculate_class_statistics(class_grades)
print(f"Class average: {stats['average']:.1f}")
print(f"Grade range: {stats['lowest']} - {stats['highest']}")
print(f"Letter grades: {stats['letter_grades']}")

List Exercises

Exercise 1: Favorite Movies Manager

favorite_movies = []

def add_movie(movie):
    """Add a movie to the favorites list."""
    # Your code here
    pass

def remove_movie(movie):
    """Remove a movie from favorites."""
    # Your code here
    pass

def show_movies():
    """Display all favorite movies."""
    # Your code here
    pass

# Test your functions
add_movie("The Matrix")
add_movie("Inception")
add_movie("Pulp Fiction")
show_movies()
remove_movie("Inception")
show_movies()

Exercise 2: Number Analyzer

def analyze_numbers(numbers):
    """Analyze a list of numbers and return statistics."""
    # Your code here
    # Return a dictionary with: sum, average, min, max, even_count, odd_count
    pass

# Test your function
test_numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result = analyze_numbers(test_numbers)
print(result)

Dictionaries: Your Digital Address Book

What are Dictionaries?

A dictionary is like a real address book or phone directory. Instead of looking up information by position (like in a list), you look up information by a unique key. For example, you look up “John Smith” to find his phone number, not “the 5th entry in the book.”

Dictionaries store data in key-value pairs, where each key is unique and points to a specific value. Think of it as a collection of labeled boxes where each label (key) is unique and tells you exactly what’s inside (value).

# Creating dictionaries - like building an address book
student_info = {
    "name": "Alice Johnson",
    "age": 20,
    "major": "Computer Science",
    "gpa": 3.8,
    "is_graduate": False
}

# Phone directory
phone_book = {
    "Alice": "555-1234",
    "Bob": "555-5678",
    "Charlie": "555-9012"
}

# Empty dictionary
empty_dict = {}

print(f"Student info: {student_info}")
print(f"Phone book: {phone_book}")

Accessing Dictionary Values

With dictionaries, you use keys instead of index numbers to access values:

# Using our student dictionary
student = {
    "name": "Alice Johnson",
    "age": 20,
    "major": "Computer Science",
    "gpa": 3.8,
    "courses": ["Python", "Calculus", "Physics"]
}

# Accessing values by key
print(f"Student name: {student['name']}")
print(f"Age: {student['age']}")
print(f"Major: {student['major']}")

# Safer way to access values (won't crash if key doesn't exist)
print(f"GPA: {student.get('gpa', 'Not available')}")
print(f"Graduation year: {student.get('grad_year', 'Not specified')}")

# Checking if a key exists
if "courses" in student:
    print(f"Current courses: {student['courses']}")

# Getting all keys and values
print(f"All keys: {list(student.keys())}")
print(f"All values: {list(student.values())}")

Modifying Dictionaries

Dictionaries are mutable (changeable), like a digital address book where you can add, update, or remove entries:

# Starting with basic student info
student = {
    "name": "Alice Johnson",
    "age": 20,
    "major": "Computer Science"
}

print(f"Original: {student}")

# Adding new information
student["gpa"] = 3.8
student["graduation_year"] = 2025
print(f"After adding GPA and grad year: {student}")

# Updating existing information
student["age"] = 21  # Birthday!
student["major"] = "Software Engineering"  # Changed major
print(f"After updates: {student}")

# Adding multiple items at once
student.update({
    "email": "alice@university.edu",
    "phone": "555-1234"
})
print(f"After adding contact info: {student}")

# Removing information
removed_phone = student.pop("phone")  # Remove and return value
print(f"Removed phone: {removed_phone}")
print(f"After removing phone: {student}")

# Remove a key-value pair (different method)
del student["email"]
print(f"After removing email: {student}")

Dictionary Methods and Operations

Dictionaries come with many useful methods for managing your data:

# Inventory system for a small store
inventory = {
    "apples": 50,
    "bananas": 30,
    "oranges": 25,
    "milk": 15,
    "bread": 20
}

# Getting information about the dictionary
print(f"Number of products: {len(inventory)}")
print(f"Products: {list(inventory.keys())}")
print(f"Quantities: {list(inventory.values())}")

# Working with key-value pairs
print("Current inventory:")
for product, quantity in inventory.items():
    print(f"- {product}: {quantity}")

# Checking stock levels
low_stock_items = []
for product, quantity in inventory.items():
    if quantity < 20:
        low_stock_items.append(product)

print(f"Low stock items: {low_stock_items}")

# Copying dictionaries
backup_inventory = inventory.copy()
print(f"Backup created: {backup_inventory}")

# Clearing all items
test_dict = {"a": 1, "b": 2}
test_dict.clear()
print(f"After clearing: {test_dict}")

Nested Dictionaries

Sometimes you need dictionaries inside dictionaries, like having multiple address books for different categories:

# Student database with multiple students
students_database = {
    "student_001": {
        "name": "Alice Johnson",
        "age": 20,
        "major": "Computer Science",
        "grades": {
            "Python": 95,
            "Calculus": 88,
            "Physics": 92
        }
    },
    "student_002": {
        "name": "Bob Smith",
        "age": 19,
        "major": "Mathematics",
        "grades": {
            "Algebra": 91,
            "Statistics": 87,
            "Geometry": 94
        }
    }
}

# Accessing nested information
alice = students_database["student_001"]
print(f"Alice's name: {alice['name']}")
print(f"Alice's Python grade: {alice['grades']['Python']}")

# Adding new nested information
students_database["student_001"]["email"] = "alice@university.edu"
students_database["student_001"]["grades"]["Chemistry"] = 89

print(f"Alice's updated info: {students_database['student_001']}")

Practical Dictionary Examples

# Word frequency counter
def count_words(text):
    """Count the frequency of each word in a text."""
    # Convert to lowercase and split into words
    words = text.lower().split()

    # Count each word
    word_count = {}
    for word in words:
        # Remove punctuation
        clean_word = word.strip(".,!?;:")
        if clean_word in word_count:
            word_count[clean_word] += 1
        else:
            word_count[clean_word] = 1

    return word_count

# Test the word counter
sample_text = "Python is great. Python is powerful. Python is easy to learn."
word_frequencies = count_words(sample_text)
print("Word frequencies:")
for word, count in word_frequencies.items():
    print(f"'{word}': {count}")

# Simple shopping cart system
shopping_cart = {}

def add_to_cart(item, quantity, price):
    """Add an item to the shopping cart."""
    shopping_cart[item] = {
        "quantity": quantity,
        "price": price,
        "total": quantity * price
    }

def remove_from_cart(item):
    """Remove an item from the shopping cart."""
    if item in shopping_cart:
        del shopping_cart[item]
        print(f"Removed {item} from cart")
    else:
        print(f"{item} not found in cart")

def show_cart():
    """Display the current shopping cart."""
    if not shopping_cart:
        print("Your cart is empty")
        return

    print("Shopping Cart:")
    total_cost = 0
    for item, details in shopping_cart.items():
        print(f"- {item}: {details['quantity']} x ${details['price']:.2f} = ${details['total']:.2f}")
        total_cost += details['total']

    print(f"Total: ${total_cost:.2f}")

# Using the shopping cart
add_to_cart("Apple", 3, 1.50)
add_to_cart("Bread", 2, 2.99)
add_to_cart("Milk", 1, 3.49)
show_cart()

remove_from_cart("Bread")
show_cart()

Python Programming Fundamentals: Part 2 – Continuation

Dictionary Exercises – Completed

Exercise 1: Personal Contact Manager

contacts = {}

def add_contact(name, phone, email):
    """Add a contact to the directory."""
    contacts[name] = {
        "phone": phone,
        "email": email
    }
    print(f"Added contact: {name}")

def update_contact(name, phone=None, email=None):
    """Update an existing contact."""
    if name in contacts:
        if phone:
            contacts[name]["phone"] = phone
        if email:
            contacts[name]["email"] = email
        print(f"Updated contact: {name}")
    else:
        print(f"Contact {name} not found")

def show_all_contacts():
    """Display all contacts."""
    if not contacts:
        print("No contacts in directory")
        return

    print("Contact Directory:")
    for name, info in contacts.items():
        print(f"Name: {name}")
        print(f"  Phone: {info['phone']}")
        print(f"  Email: {info['email']}")
        print()

# Test your functions
add_contact("Alice", "555-1234", "alice@email.com")
add_contact("Bob", "555-5678", "bob@email.com")
show_all_contacts()
update_contact("Alice", phone="555-9999")
show_all_contacts()

Exercise 2: Grade Book System

gradebook = {}

def add_student(name):
    """Add a new student to the gradebook."""
    if name not in gradebook:
        gradebook[name] = {}
        print(f"Added student: {name}")
    else:
        print(f"Student {name} already exists")

def add_grade(student_name, subject, grade):
    """Add a grade for a student in a specific subject."""
    if student_name in gradebook:
        gradebook[student_name][subject] = grade
        print(f"Added grade for {student_name} in {subject}: {grade}")
    else:
        print(f"Student {student_name} not found")

def calculate_average(student_name):
    """Calculate the average grade for a student."""
    if student_name in gradebook and gradebook[student_name]:
        grades = list(gradebook[student_name].values())
        average = sum(grades) / len(grades)
        return average
    else:
        return None

def show_student_grades(student_name):
    """Display all grades for a specific student."""
    if student_name in gradebook:
        print(f"Grades for {student_name}:")
        for subject, grade in gradebook[student_name].items():
            print(f"  {subject}: {grade}")

        average = calculate_average(student_name)
        if average:
            print(f"  Average: {average:.1f}")
    else:
        print(f"Student {student_name} not found")

# Test the gradebook system
add_student("Alice")
add_student("Bob")
add_grade("Alice", "Math", 95)
add_grade("Alice", "Science", 88)
add_grade("Alice", "English", 92)
show_student_grades("Alice")

Tuples: Unchangeable Data Containers

What are Tuples?

Think of a tuple as a sealed envelope containing important documents. Once you seal it, you can’t change what’s inside, but you can still read the contents. Tuples are like lists, but they’re immutable, meaning you can’t change them after creation.

This immutability makes tuples perfect for storing data that shouldn’t change, like coordinates, RGB color values, or database records. They’re also slightly faster than lists and use less memory.

# Creating tuples - like sealing important information
point_coordinates = (10, 20)  # A point in 2D space
rgb_color = (255, 128, 0)     # Orange color values
student_record = ("Alice", 20, "Computer Science", 3.8)

# Tuples can be created without parentheses too
another_point = 5, 15
single_item_tuple = (42,)  # Note the comma - this is important!

print(f"Point coordinates: {point_coordinates}")
print(f"RGB color: {rgb_color}")
print(f"Student record: {student_record}")
print(f"Single item tuple: {single_item_tuple}")

Accessing Tuple Elements

You access tuple elements the same way you access list elements, using square brackets and index numbers:

# Working with a tuple representing a book record
book_info = ("1984", "George Orwell", 1949, "Dystopian Fiction")

# Accessing individual elements
title = book_info[0]
author = book_info[1]
year = book_info[2]
genre = book_info[3]

print(f"Book: {title}")
print(f"Author: {author}")
print(f"Published: {year}")
print(f"Genre: {genre}")

# Tuple unpacking - a powerful feature
title, author, year, genre = book_info
print(f"Using unpacking - Book: {title} by {author} ({year})")

# Partial unpacking with the rest operator
first_name, last_name, *other_info = ("John", "Smith", 30, "Engineer", "New York")
print(f"Name: {first_name} {last_name}")
print(f"Other info: {other_info}")

Tuple Methods and Operations

While you can’t change tuples, you can still work with them in many ways:

# Sample tuple with some repeated values
grades = (85, 90, 78, 90, 92, 85, 88)

# Getting information about the tuple
print(f"Number of grades: {len(grades)}")
print(f"Highest grade: {max(grades)}")
print(f"Lowest grade: {min(grades)}")

# Counting occurrences
print(f"Number of 90s: {grades.count(90)}")
print(f"Number of 85s: {grades.count(85)}")

# Finding the position of an element
print(f"First occurrence of 90: {grades.index(90)}")

# Checking if an element exists
print(f"Is 95 in grades? {95 in grades}")
print(f"Is 85 in grades? {85 in grades}")

# Slicing works with tuples too
print(f"First three grades: {grades[:3]}")
print(f"Last three grades: {grades[-3:]}")

Practical Tuple Examples

# Representing geographic coordinates
def get_distance_between_points(point1, point2):
    """Calculate distance between two points using tuples."""
    x1, y1 = point1
    x2, y2 = point2

    # Using the distance formula
    distance = ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5
    return distance

# Using coordinate tuples
home = (0, 0)
work = (3, 4)
store = (1, 2)

print(f"Distance from home to work: {get_distance_between_points(home, work):.2f}")
print(f"Distance from home to store: {get_distance_between_points(home, store):.2f}")

# Function that returns multiple values using tuples
def analyze_text(text):
    """Analyze text and return statistics as a tuple."""
    words = text.split()
    word_count = len(words)
    char_count = len(text)
    sentence_count = text.count('.') + text.count('!') + text.count('?')

    return (word_count, char_count, sentence_count)

# Using the function
sample_text = "Python is amazing! It's powerful and easy to learn. Try it today!"
word_count, char_count, sentence_count = analyze_text(sample_text)

print(f"Text analysis:")
print(f"Words: {word_count}")
print(f"Characters: {char_count}")
print(f"Sentences: {sentence_count}")

# Database-like records using tuples
employee_records = [
    ("Alice", "Johnson", "Software Engineer", 75000),
    ("Bob", "Smith", "Data Scientist", 80000),
    ("Charlie", "Brown", "Product Manager", 85000)
]

print("Employee Directory:")
for first_name, last_name, position, salary in employee_records:
    print(f"{first_name} {last_name} - {position} - ${salary:,}")

Sets: Collections of Unique Items

What are Sets?

Imagine you’re collecting unique stamps for your hobby. You wouldn’t want duplicate stamps in your collection, right? A set is like a special container that automatically prevents duplicates. Sets are perfect when you need to ensure all items are unique or when you want to perform mathematical operations like finding common elements between groups.

# Creating sets - like building unique collections
unique_numbers = {1, 2, 3, 4, 5}
colors = {"red", "blue", "green", "yellow"}
mixed_set = {1, "hello", 3.14, True}

# Creating sets from lists (removes duplicates automatically)
numbers_with_duplicates = [1, 2, 2, 3, 3, 3, 4, 5]
unique_numbers_from_list = set(numbers_with_duplicates)

# Empty set (note: {} creates an empty dictionary, not set)
empty_set = set()

print(f"Unique numbers: {unique_numbers}")
print(f"Colors: {colors}")
print(f"From list with duplicates: {unique_numbers_from_list}")
print(f"Empty set: {empty_set}")

Set Operations

Sets support mathematical operations that are incredibly useful for comparing and combining data:

# Student enrollment in different courses
python_students = {"Alice", "Bob", "Charlie", "Diana"}
java_students = {"Bob", "Charlie", "Eve", "Frank"}
javascript_students = {"Charlie", "Diana", "Eve", "Grace"}

# Union - students taking ANY programming course
all_programming_students = python_students | java_students | javascript_students
print(f"All programming students: {all_programming_students}")

# Intersection - students taking BOTH Python AND Java
python_and_java = python_students & java_students
print(f"Students taking both Python and Java: {python_and_java}")

# Difference - students taking Python but NOT Java
python_only = python_students - java_students
print(f"Students taking Python but not Java: {python_only}")

# Symmetric difference - students taking Python OR Java but not both
python_xor_java = python_students ^ java_students
print(f"Students taking Python or Java but not both: {python_xor_java}")

Modifying Sets

Sets are mutable, so you can add and remove elements:

# Starting with a basic set of skills
my_skills = {"Python", "JavaScript", "HTML"}
print(f"Initial skills: {my_skills}")

# Adding single skills
my_skills.add("CSS")
my_skills.add("React")
print(f"After learning CSS and React: {my_skills}")

# Adding multiple skills at once
new_skills = {"Node.js", "MongoDB", "Docker"}
my_skills.update(new_skills)
print(f"After bootcamp: {my_skills}")

# Removing skills (maybe you forgot some!)
my_skills.remove("HTML")  # Raises error if not found
print(f"After forgetting HTML: {my_skills}")

# Safer removal (won't raise error if not found)
my_skills.discard("PHP")  # Won't cause error even though PHP isn't in the set
print(f"After trying to remove PHP: {my_skills}")

# Removing and returning an arbitrary element
removed_skill = my_skills.pop()
print(f"Randomly removed skill: {removed_skill}")
print(f"Remaining skills: {my_skills}")

Practical Set Examples

# Email subscription management
def manage_email_subscriptions():
    """Manage different email subscription lists."""
    newsletter_subscribers = {"alice@email.com", "bob@email.com", "charlie@email.com"}
    promotion_subscribers = {"bob@email.com", "diana@email.com", "eve@email.com"}

    # Find subscribers who get both newsletters and promotions
    both_subscriptions = newsletter_subscribers & promotion_subscribers
    print(f"Subscribers getting both: {both_subscriptions}")

    # Find all unique subscribers
    all_subscribers = newsletter_subscribers | promotion_subscribers
    print(f"All subscribers: {all_subscribers}")

    # Find newsletter-only subscribers
    newsletter_only = newsletter_subscribers - promotion_subscribers
    print(f"Newsletter only: {newsletter_only}")

    return all_subscribers

# Word uniqueness checker
def find_unique_words(text1, text2):
    """Find unique words in two texts."""
    # Convert to lowercase and split into words
    words1 = set(text1.lower().split())
    words2 = set(text2.lower().split())

    # Clean up punctuation
    words1 = {word.strip(".,!?;:") for word in words1}
    words2 = {word.strip(".,!?;:") for word in words2}

    # Find common words
    common_words = words1 & words2

    # Find unique words in each text
    unique_to_text1 = words1 - words2
    unique_to_text2 = words2 - words1

    return {
        "common": common_words,
        "unique_to_first": unique_to_text1,
        "unique_to_second": unique_to_text2
    }

# Test the word analyzer
text1 = "Python is a great programming language for beginners"
text2 = "Java is also a popular programming language for developers"

word_analysis = find_unique_words(text1, text2)
print("Word Analysis:")
print(f"Common words: {word_analysis['common']}")
print(f"Unique to first text: {word_analysis['unique_to_first']}")
print(f"Unique to second text: {word_analysis['unique_to_second']}")

# Inventory system using sets
def track_inventory_categories():
    """Track different categories of products in inventory."""
    electronics = {"laptop", "phone", "tablet", "headphones"}
    discounted_items = {"laptop", "book", "shirt", "headphones"}
    popular_items = {"phone", "book", "laptop", "shoes"}

    # Find electronics that are on discount
    discounted_electronics = electronics & discounted_items
    print(f"Discounted electronics: {discounted_electronics}")

    # Find popular electronics
    popular_electronics = electronics & popular_items
    print(f"Popular electronics: {popular_electronics}")

    # Find all product categories
    all_products = electronics | discounted_items | popular_items
    print(f"All products: {all_products}")

manage_email_subscriptions()
track_inventory_categories()

Working with Multiple Data Structures

Combining Different Data Structures

In real-world programming, you often need to combine different data structures to solve complex problems. Think of this like using different tools from your toolbox together to build something sophisticated.

# Complex student management system
class_roster = {
    "CS101": {
        "course_name": "Introduction to Programming",
        "students": ["Alice", "Bob", "Charlie"],
        "assignments": [
            {"name": "Hello World", "due_date": "2024-02-15", "points": 10},
            {"name": "Variables and Types", "due_date": "2024-02-22", "points": 25}
        ],
        "grades": {
            "Alice": [10, 23],
            "Bob": [8, 22],
            "Charlie": [10, 25]
        }
    },
    "CS102": {
        "course_name": "Data Structures",
        "students": ["Alice", "Diana", "Eve"],
        "assignments": [
            {"name": "Arrays", "due_date": "2024-02-20", "points": 30},
            {"name": "Linked Lists", "due_date": "2024-02-27", "points": 35}
        ],
        "grades": {
            "Alice": [28, 33],
            "Diana": [30, 35],
            "Eve": [25, 30]
        }
    }
}

def calculate_student_average(course_code, student_name):
    """Calculate average grade for a student in a specific course."""
    if course_code in class_roster and student_name in class_roster[course_code]["grades"]:
        grades = class_roster[course_code]["grades"][student_name]
        total_points = sum(grades)

        # Calculate total possible points
        assignments = class_roster[course_code]["assignments"]
        total_possible = sum(assignment["points"] for assignment in assignments)

        percentage = (total_points / total_possible) * 100
        return percentage
    else:
        return None

def find_common_students(*course_codes):
    """Find students enrolled in multiple courses."""
    if not course_codes:
        return set()

    # Start with students from first course
    common_students = set(class_roster[course_codes[0]]["students"])

    # Find intersection with other courses
    for course_code in course_codes[1:]:
        course_students = set(class_roster[course_code]["students"])
        common_students &= course_students

    return common_students

# Using the system
print("Alice's average in CS101:", f"{calculate_student_average('CS101', 'Alice'):.1f}%")
print("Students in both CS101 and CS102:", find_common_students("CS101", "CS102"))

# Recipe management system combining all data structures
recipe_database = {
    "Pancakes": {
        "ingredients": [
            ("flour", 2, "cups"),
            ("milk", 1.5, "cups"),
            ("eggs", 2, "pieces"),
            ("sugar", 2, "tablespoons")
        ],
        "instructions": [
            "Mix dry ingredients in a bowl",
            "Combine wet ingredients separately",
            "Gradually add wet to dry ingredients",
            "Cook on griddle until golden"
        ],
        "prep_time": 10,
        "cook_time": 15,
        "servings": 4,
        "dietary_tags": {"vegetarian"}
    },
    "Chicken Stir Fry": {
        "ingredients": [
            ("chicken breast", 1, "pound"),
            ("broccoli", 2, "cups"),
            ("soy sauce", 3, "tablespoons"),
            ("garlic", 2, "cloves")
        ],
        "instructions": [
            "Cut chicken into bite-sized pieces",
            "Heat oil in wok or large pan",
            "Cook chicken until done",
            "Add vegetables and sauce"
        ],
        "prep_time": 15,
        "cook_time": 12,
        "servings": 3,
        "dietary_tags": {"gluten-free", "high-protein"}
    }
}

def find_recipes_by_time(max_total_time):
    """Find recipes that can be made within a time limit."""
    suitable_recipes = []

    for recipe_name, recipe_info in recipe_database.items():
        total_time = recipe_info["prep_time"] + recipe_info["cook_time"]
        if total_time <= max_total_time:
            suitable_recipes.append((recipe_name, total_time))

    # Sort by total time
    suitable_recipes.sort(key=lambda x: x[1])
    return suitable_recipes

def get_shopping_list(recipe_names):
    """Generate shopping list for multiple recipes."""
    shopping_list = {}

    for recipe_name in recipe_names:
        if recipe_name in recipe_database:
            for ingredient, amount, unit in recipe_database[recipe_name]["ingredients"]:
                if ingredient in shopping_list:
                    # Simple aggregation (assumes same units)
                    shopping_list[ingredient] += amount
                else:
                    shopping_list[ingredient] = amount

    return shopping_list

# Using the recipe system
quick_recipes = find_recipes_by_time(30)
print("Recipes under 30 minutes:")
for recipe, time in quick_recipes:
    print(f"- {recipe}: {time} minutes")

shopping_list = get_shopping_list(["Pancakes", "Chicken Stir Fry"])
print("\nShopping list:")
for ingredient, amount in shopping_list.items():
    print(f"- {ingredient}: {amount}")

Data Structure Selection Guide

Understanding when to use each data structure is crucial for efficient programming:

# Practical examples of choosing the right data structure

# Use LIST when:
# - You need ordered data
# - You need to access items by position
# - You need to modify the collection frequently
shopping_cart_items = ["apple", "bread", "milk", "eggs"]  # Order matters for checkout

# Use DICTIONARY when:
# - You need fast lookups by key
# - You're storing related attributes
# - You need to map one thing to another
student_grades = {"Alice": 95, "Bob": 87, "Charlie": 92}  # Fast grade lookups

# Use TUPLE when:
# - Data shouldn't change
# - You need to return multiple values from a function
# - You're representing fixed records
coordinates = (10, 20)  # Point coordinates shouldn't change arbitrarily

# Use SET when:
# - You need unique items only
# - You need to find common or different items between groups
# - You need fast membership testing
unique_visitors = {"user1", "user2", "user3"}  # No duplicate visitors

def demonstrate_data_structure_choice():
    """Show practical examples of choosing the right data structure."""

    # Scenario 1: Event planning
    print("Event Planning System:")

    # Guest list (order matters for seating)
    guest_list = ["Alice", "Bob", "Charlie", "Diana"]

    # RSVP responses (fast lookup by name)
    rsvp_responses = {
        "Alice": "Yes",
        "Bob": "No", 
        "Charlie": "Yes",
        "Diana": "Maybe"
    }

    # Dietary restrictions (unique items only)
    dietary_restrictions = {"vegetarian", "gluten-free", "vegan"}

    # Event details (immutable record)
    event_details = ("Wedding Reception", "2024-06-15", "Grand Ballroom")

    print(f"Event: {event_details[0]} on {event_details[1]}")
    print(f"Confirmed guests: {[guest for guest in guest_list if rsvp_responses.get(guest) == 'Yes']}")
    print(f"Dietary restrictions to consider: {dietary_restrictions}")

demonstrate_data_structure_choice()

What’s Next in Part 3

Congratulations! You’ve now mastered the essential building blocks of Python programming. You can create reusable functions, understand how variables work in different scopes, and organize your data using lists, dictionaries, tuples, and sets.

In Part 3 of this series, we’ll explore more advanced topics that will help you write even more powerful and professional Python programs:

File Input/Output: Learn how to read from and write to files, handle different file formats, and manage file operations safely.

Error Handling: Discover how to gracefully handle errors and exceptions in your programs, making them more robust and user-friendly.

Object-Oriented Programming: Understand classes and objects, which will allow you to create more complex and organized programs.

Working with Libraries: Explore how to use external libraries to extend Python’s capabilities and solve real-world problems more efficiently.

Project Development: Put everything together in practical projects that demonstrate professional programming practices.

Remember, programming is like learning a musical instrument – the more you practice, the more natural it becomes. Try to write small programs using the concepts you’ve learned, experiment with different combinations of data structures, and don’t be afraid to make mistakes. Each error is a learning opportunity that brings you closer to becoming a proficient programmer.

Link to part 1 https://dev.to/fonyuygita/python-programming-fundamentals-a-complete-beginners-guide-part-1-2h3k

Keep practicing, stay curious, and happy coding!


This content originally appeared on DEV Community and was authored by Fonyuy Gita