Week 2 Friday Afternoon: Review of Python Fundamentals

Instructor's Notes: A comprehensive review of Week 2 concepts

Introduction to Our Journey So Far

Welcome to our Week 2 comprehensive review! This week has been a foundational journey through the Python landscape. Think of what we've learned as constructing the framework of a house - we've laid the foundation (basic syntax), built the supporting walls (data structures), installed the electrical system (control flow), and designed the rooms (functions).

Today we'll take a step back and examine our construction project as a whole, connecting concepts and reinforcing the key learnings that will support everything else we build as Python developers.

Python as a Programming Ecosystem

We started our week understanding that Python is more than just a language - it's an ecosystem. Like a thriving forest with interdependent species, Python consists of the core language, its standard library, and a vast community of third-party packages.

Core Python Philosophy

Remember the Zen of Python? These guiding principles shape how we write "Pythonic" code:

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
...

In the real world, these principles mean that Python developers value code that is:

Real-world analogy: Think of Python as a well-designed kitchen. All tools are easily accessible, clearly labeled, and designed for specific purposes. Other languages might give you a Swiss Army knife with hidden tools; Python gives you a kitchen where everything has its place and purpose.

Variables, Data Types, and Basic Operations

Variables in Python are like labeled containers. Unlike some languages that make you declare what type of container you need before using it, Python lets you use whatever container size is appropriate for what you're storing.

Numbers, Strings and Basic Operations

We explored how Python handles numbers (integers, floats) and text (strings). Remember these key points:

The way Python handles these types has real-world implications. For example, if you're building a financial application:

# Floating point arithmetic can lead to unexpected results
0.1 + 0.2  # Results in 0.30000000000000004, not exactly 0.3

# For financial calculations, use Decimal instead
from decimal import Decimal
Decimal('0.1') + Decimal('0.2')  # Results in Decimal('0.3')

Real-world example: A retail point-of-sale system would use Decimal for all price calculations to avoid penny rounding errors that could, at scale, cause significant accounting discrepancies.

Control Structures: Decision Making and Repetition

Control structures are like the traffic lights and road patterns of your code - they direct the flow of execution.

Conditional Logic

We examined how to make decisions in Python using if, elif, and else statements:

weather = "rainy"

if weather == "sunny":
    print("Wear sunscreen")
elif weather == "rainy":
    print("Bring an umbrella")
elif weather == "snowy":
    print("Wear a coat")
else:
    print("Check the forecast again")

Real-world application: E-commerce site product recommendations use complex conditional logic to analyze user behavior and suggest relevant products. The logic might evaluate purchase history, browsing patterns, and demographic information to personalize the shopping experience.

Loops for Repetition

We explored how to repeat actions using loops:

# For loop - when you know how many iterations you need
for fruit in ["apple", "banana", "cherry"]:
    print(f"I like {fruit}s")

# While loop - when you need to continue until a condition is met
attempts = 0
while attempts < 3:
    password = input("Enter password: ")
    if password == "secret":
        print("Access granted")
        break
    attempts += 1
else:
    print("Too many failed attempts")

Notice how the for loop is ideal when iterating through a collection, while the while loop shines when you need to repeat until a condition changes.

Real-world example: Data processing pipelines often use loops to transform large datasets. Imagine processing thousands of customer records, where each record needs validation, transformation, and database insertion.

Data Structures: Collections of Information

Python's built-in data structures are like different types of containers, each optimized for specific use cases.

Lists

Lists are ordered, mutable collections - like a train with cars that can be added, removed, or rearranged:

shopping_list = ["milk", "eggs", "bread"]
shopping_list.append("cheese")  # Add to the end
shopping_list.insert(0, "apples")  # Add at a specific position
print(shopping_list[1])  # Access by index

Real-world application: Task management applications use list-like structures to track user tasks, allowing for additions, completions, and reordering of priorities.

Tuples

Tuples are immutable ordered collections - like sealed packages that can't be changed after creation:

coordinates = (34.0522, -118.2437)  # Los Angeles coordinates
name, age = ("Alice", 29)  # Tuple unpacking

Real-world application: Geographic information systems use tuples to represent coordinates because latitude and longitude shouldn't be accidentally swapped or modified.

Dictionaries

Dictionaries are key-value mappings - like a phone book where you can look up information by name:

user = {
    "username": "python_lover",
    "email": "dev@example.com",
    "active": True,
    "login_count": 42
}

print(user["email"])  # Access by key
user["last_login"] = "2023-08-25"  # Add new key-value pair

Real-world application: Content management systems often store page data in dictionary-like structures, with keys for title, content, author, publication date, etc.

Sets

Sets are unordered collections of unique elements - like a basket that automatically rejects duplicates:

unique_visitors = {"user123", "user456", "user789"}
unique_visitors.add("user123")  # Will not add duplicate
print(len(unique_visitors))  # Still just 3 items

# Set operations
android_users = {"user123", "user456", "user999"}
ios_users = {"user456", "user789", "user888"}

# Users on both platforms
cross_platform = android_users.intersection(ios_users)
# Users on either platform
all_users = android_users.union(ios_users)

Real-world application: Analytics platforms use sets to track unique website visitors, ensuring each visitor is counted only once regardless of how many times they access the site.

Functions: Reusable Code Blocks

Functions are like specialized machines in a factory - they take input, perform specific operations, and produce output.

Defining and Using Functions

def calculate_shipping(weight, distance, express=False):
    """
    Calculate shipping cost based on weight, distance, and shipping method.
    
    Args:
        weight (float): Weight in kilograms
        distance (float): Distance in kilometers
        express (bool, optional): Whether express shipping is requested
    
    Returns:
        float: The calculated shipping cost
    """
    base_rate = 5.00
    weight_factor = 2.50 * weight
    distance_factor = 0.01 * distance
    
    if express:
        return (base_rate + weight_factor + distance_factor) * 1.5
    else:
        return base_rate + weight_factor + distance_factor

# Using the function
regular_cost = calculate_shipping(2.5, 150)
express_cost = calculate_shipping(2.5, 150, express=True)

Notice the docstring that explains what the function does, its parameters, and return value. This is essential for making your code maintainable and usable by others (including your future self).

Real-world application: E-commerce platforms use similar functions to calculate shipping costs based on product weight, delivery distance, and selected shipping options.

Lambda Functions

Lambda functions are like disposable tools - small, anonymous functions for simple operations:

# Sort a list of products by price
products = [
    {"name": "Laptop", "price": 1200},
    {"name": "Phone", "price": 800},
    {"name": "Tablet", "price": 500}
]

# Sort using a lambda function
products_by_price = sorted(products, key=lambda item: item["price"])

# Using lambda with filter
affordable = list(filter(lambda item: item["price"] < 1000, products))

Real-world application: Data analysis often uses lambda functions with map/filter/reduce operations to transform datasets without writing numerous small named functions.

Modules and Packages: Code Organization

As your codebase grows, organization becomes crucial. Modules and packages are like filing cabinets for your code.

Creating and Importing Modules

Let's imagine we have these files in our project:

# File: utils.py
def format_currency(amount):
    """Format amount as USD currency string"""
    return f"${amount:.2f}"

def calculate_tax(amount, rate=0.08):
    """Calculate tax amount based on rate"""
    return amount * rate
# File: app.py
import utils

# Using functions from the module
price = 42.99
tax = utils.calculate_tax(price)
total = price + tax
formatted_total = utils.format_currency(total)

print(f"Total price: {formatted_total}")

Real-world application: Frameworks like Django organize code into functional modules (models, views, controllers) to maintain clean separation of concerns in complex web applications.

Virtual Environments and Dependencies

We explored how virtual environments keep project dependencies isolated:

# Creating a virtual environment
python -m venv myproject_env

# Activating it (on Windows)
myproject_env\Scripts\activate

# Activating it (on macOS/Linux)
source myproject_env/bin/activate

# Installing dependencies
pip install requests pandas matplotlib

# Saving dependencies list
pip freeze > requirements.txt

Real-world application: Deployment pipelines for production applications use requirements.txt files to ensure consistent environments across development, staging, and production servers.

Docker and Dependencies Management

Going beyond virtual environments, Docker takes isolation to the next level by containerizing not just the Python environment but the entire operating system environment.

Basic Dockerfile for Python Applications

# File: Dockerfile
FROM python:3.10

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["python", "app.py"]

Real-world application: Microservice architectures use Docker containers to deploy small, focused services that can be developed, updated, and scaled independently of each other.

Docker Compose for Development Environments

# File: docker-compose.yml
version: '3'

services:
  web:
    build: .
    ports:
      - "5000:5000"
    volumes:
      - .:/app
    environment:
      - FLASK_ENV=development
      - DATABASE_URL=postgresql://user:password@db:5432/app_db
    depends_on:
      - db
  
  db:
    image: postgres:14
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=app_db
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

Real-world application: Development teams use Docker Compose to create consistent environments that match production, eliminating the "it works on my machine" problem.

Project Structure Best Practices

As your projects grow, organization becomes crucial. Let's examine a typical Python project structure:

myproject/
│
├── myproject/          # Main package
│   ├── __init__.py
│   ├── core.py
│   ├── helpers.py
│   └── subpackage/
│       ├── __init__.py
│       └── feature.py
│
├── tests/              # Test files
│   ├── test_core.py
│   └── test_helpers.py
│
├── docs/               # Documentation
│   └── index.md
│
├── .gitignore          # Git ignore file
├── README.md           # Project description
├── requirements.txt    # Dependencies
├── setup.py            # Installation script
└── Dockerfile          # Container definition

Real-world application: Open-source libraries follow consistent project structures to make contribution and adoption easier for the community.

The Importance of README Files

A good README.md file typically includes:

Real-world application: Documentation-first development approaches write the README before writing code, ensuring the project has a clear purpose and interface.

Comparison: Python vs JavaScript Dependencies

For those coming from JavaScript backgrounds, let's compare dependency management approaches:

Feature Python JavaScript
Package Manager pip npm/yarn
Dependencies File requirements.txt package.json
Lock File Pipfile.lock (with pipenv) package-lock.json / yarn.lock
Environment Isolation virtualenv, venv node_modules directory
Version Specification requests>=2.25.1,<3.0.0 "express": "^4.17.1"

Real-world application: Full-stack developers working with both Python backends and JavaScript frontends need to understand both dependency systems to maintain project coherence.

Practical Exercise: Building a Command-Line Tool

Let's tie everything together with a practical example: a command-line tool that reads CSV data, performs analysis, and outputs results.

# File: data_analyzer.py
import argparse
import csv
from collections import defaultdict
import json
import sys


def read_csv_data(file_path):
    """Read CSV file and return list of dictionaries"""
    try:
        with open(file_path, 'r', newline='') as file:
            reader = csv.DictReader(file)
            return list(reader)
    except FileNotFoundError:
        print(f"Error: File {file_path} not found.")
        sys.exit(1)
    except Exception as e:
        print(f"Error reading file: {e}")
        sys.exit(1)


def analyze_sales_by_category(data):
    """Group sales by category and calculate totals"""
    sales_by_category = defaultdict(float)
    
    for row in data:
        try:
            category = row['Category']
            amount = float(row['Amount'])
            sales_by_category[category] += amount
        except (KeyError, ValueError) as e:
            print(f"Warning: Could not process row - {e}")
            continue
    
    # Convert to regular dict and sort by value
    return dict(sorted(
        sales_by_category.items(), 
        key=lambda item: item[1], 
        reverse=True
    ))


def main():
    # Set up argument parser
    parser = argparse.ArgumentParser(description='Analyze sales data from CSV files')
    parser.add_argument('file', help='CSV file to analyze')
    parser.add_argument('--output', choices=['text', 'json'], default='text',
                      help='Output format (default: text)')
    
    args = parser.parse_args()
    
    # Read and analyze data
    data = read_csv_data(args.file)
    results = analyze_sales_by_category(data)
    
    # Output results in requested format
    if args.output == 'json':
        print(json.dumps(results, indent=2))
    else:
        print("Sales by Category:")
        for category, total in results.items():
            print(f"{category}: ${total:.2f}")


if __name__ == "__main__":
    main()

Example usage:

# Running the tool
python data_analyzer.py sales_data.csv

# With JSON output format
python data_analyzer.py sales_data.csv --output json

This example incorporates many of the concepts we've learned:

Real-world application: Data engineers build similar tools for ETL (Extract, Transform, Load) processes to prepare data for analytics platforms and dashboards.

Common Pitfalls and Best Practices

Mutable Default Arguments

One common Python gotcha is using mutable objects as default arguments:

# Problematic - the list is created once when the function is defined
def add_user_to_group(user, group=[]):
    group.append(user)
    return group

# First call
team_a = add_user_to_group("Alice")  # Returns ["Alice"]

# Second call - you might expect a new list
team_b = add_user_to_group("Bob")    # Returns ["Alice", "Bob"] - not what you wanted!

# Better approach
def add_user_to_group_fixed(user, group=None):
    if group is None:
        group = []
    group.append(user)
    return group

Real-world impact: This subtle bug can cause data leakage between different users or requests in web applications, potentially exposing private information.

PEP 8 Style Guide

Following the PEP 8 style guide makes your code more readable and maintainable:

Real-world impact: Most professional Python teams enforce PEP 8 compliance through linters and code reviews, so following these conventions makes your code more employable.

Next Steps in Your Python Journey

As we conclude Week 2, you're now equipped with the core building blocks of Python programming. Next week, we'll dive deeper into:

In the meantime, practice what you've learned by:

  1. Refactoring your previous assignments to use functions and modules
  2. Converting a script-based solution to use proper command-line arguments
  3. Creating a small utility that solves a real problem you have

Real-world insight: Professional developers spend more time reading and modifying code than writing new code from scratch. Practice reading other people's code (like open-source projects) to build this crucial skill.

Weekend Project: Building a Command-Line Utility

Your weekend project is to create a command-line tool that demonstrates multiple Python concepts from this week. Choose one of these options or propose your own:

  1. File Organization Tool: Create a script that organizes files in a directory based on their extension, creation date, or other criteria
  2. Personal Finance Tracker: Build a tool to track expenses from CSV bank exports, categorize them, and report spending patterns
  3. URL Shortener: Develop a utility that shortens URLs and stores the mappings in a local file database
  4. Weather Reporter: Create a tool that fetches weather data for a location and presents it in a readable format

Your project should include:

This project will serve as a portfolio piece demonstrating your Python skills!