python

Introduction To python

wwww

  1. Learn Python with AI Made Easy
    1. Understanding Python and Its Industry Importance
      1. Why Python Matters in Industry
  2. Introduction
    1. 1.1 What is Python
    2. 1.2 History of Python
    3. 1.3 Why Python is Popular
    4. 1.4 Key Features of Python
    5. 1.5 Python vs Other Languages (C, C++, Java)
    6. 1.6 Python Applications (Web, Data Science, AI, Automation, Cybersecurity)
  3. Detailed Setup and First Application
    1. 2.1 Prerequisites for Environment Setup
    2. 2.2 Setup on Linux (Ubuntu) – Command-Line Environment
      1. Step 1: Update System Packages
      2. Step 2: Install Python 3
      3. Step 3: Verify Installation
      4. Step 4: Create a Project Folder
      5. Step 5: Create Your First Python Script
      6. Step 6: Save and Exit
      7. Step 7: Run the Program
      8. Step 8 (Optional): Make the Script Executable
    3. 2.3 Setup on Linux – Professional IDE Environment
      1. Step 1: Install VS Code
      2. Step 2: Install the Python Extension
      3. Step 3: Open Your Project Folder
      4. Step 4: Create a New Python File
      5. Step 5: Write Your Code
      6. Step 6: Run the Code
      7. Step 7: Debug Your Code
    4. 2.4 Setup on Linux – AI-Integrated Development Workflow
      1. Step 1: Create Required Accounts
      2. Step 2: Install GitHub Copilot Extension in VS Code
      3. Step 3: Authenticate Copilot
      4. Step 4: Generate Code Using Copilot
      5. Step 5: Use ChatGPT for Explanations and Debugging
      6. Step 6: Refactor Code with AI
    5. 2.5 Setup on Windows – Command-Line Environment
      1. Step 1: Download Python Installer
      2. Step 2: Run the Installer
      3. Step 3: Verify Installation
      4. Step 4: Create a Project Folder
      5. Step 5: Create Your First Python Script
      6. Step 6: Run the Program
      7. Step 7 (Optional): Use Python Interactive Mode
    6. 2.6 Setup on Windows – Professional IDE Environment
      1. Step 1: Install Visual Studio Code
      2. Step 2: Install the Python Extension
      3. Step 3: Open Your Project Folder
      4. Step 4: Create a Python File
      5. Step 5: Run the Code
      6. Step 6: Set Up Debugging
    7. 2.7 Setup on Windows – AI-Integrated Development Workflow
      1. Step 1: Install GitHub Copilot Extension
      2. Step 2: Sign In to GitHub
      3. Step 3: Generate Code with Copilot
      4. Step 4: Use ChatGPT for Debugging
      5. Step 5: Refactor with AI
    8. 2.8 Setup on macOS – Command-Line Environment
      1. Step 1: Install Homebrew (Package Manager)
      2. Step 2: Install Python 3 via Homebrew
      3. Step 3: Verify Installation
      4. Step 4: Create a Project Folder
      5. Step 5: Create a Python Script
      6. Step 6: Run the Program
    9. 2.9 Setup on macOS – Professional IDE Environment
      1. Step 1: Install VS Code
      2. Step 2: Install Python Extension
      3. Step 3: Open Project Folder
      4. Step 4: Create and Run hello.py
      5. Step 5: Use the Integrated Debugger
    10. 2.10 Setup on macOS – AI-Integrated Development Workflow
      1. Step 1: Install GitHub Copilot in VS Code
      2. Step 2: Generate Code with AI
      3. Step 3: Use ChatGPT to Understand Code
      4. Step 4: Optimize with AI
    11. 2.11 Python Interpreter and Running Programs
    12. 2.12 First Python Program (Hello World)
    13. 2.13 Software Execution Lifecycle
  4. AI Integration with AI Tools
    1. 3.1 ChatGPT Workflows
    2. 3.2 GitHub Copilot
    3. 3.3 AI Debugging
    4. 3.4 AI Testing
    5. 3.5 AI Refactoring
  5. Python Programming Basics
    1. 4.1 Python Syntax Rules
    2. 4.2 Indentation in Python
    3. 4.3 Variables
    4. 4.4 Naming Conventions
    5. 4.5 Python Keywords
    6. 4.6 Type Casting
  6. Data Types & Operators
    1. 5.1 Numeric Types (int, float, complex)
    2. 5.2 Boolean Type
    3. 5.3 Sequence Types (string, list, tuple, range)
      1. String
      2. List
      3. Tuple
      4. Range
    4. 5.4 Set Types
    5. 5.5 Mapping Type – Dictionary
    6. 5.6 Arithmetic Operators
    7. 5.7 Comparison Operators
    8. 5.8 Logical Operators
    9. 5.9 Assignment Operators
    10. 5.10 Bitwise Operators
    11. 5.11 Identity Operators
    12. 5.12 Membership Operators
  7. Control Flow
    1. 6.1 Conditional Statements (if, if-else, elif ladder, nested conditions)
    2. 6.2 Loops (for, while)
      1. For Loop
      2. While Loop
    3. 6.3 Loop Control Statements (break, continue, pass)
  8. Functions & Modular Programming
    1. 7.1 Defining Functions
    2. 7.2 Parameters & Arguments
      1. Positional Arguments
      2. Keyword Arguments
      3. Default Arguments
      4. Variable-Length Arguments
    3. 7.3 Return Values
    4. 7.4 Lambda Functions
    5. 7.5 Recursion
  9. Python Data Structures
    1. 8.1 Lists
    2. 8.2 Tuples
    3. 8.3 Sets
    4. 8.4 Dictionaries
  10. Strings & Text Processing
    1. 9.1 String Basics (indexing, slicing)
    2. 9.2 String Methods (upper, lower, split, join, replace, etc.)
    3. 9.3 String Formatting (f-strings, format() method)
    4. 9.4 Regular Expressions (re module)
  11. Modules & Packages
    1. 10.1 Creating Modules
    2. 10.2 Importing Modules
    3. 10.3 Built-in Modules (math, random, datetime, os, sys)
    4. 10.4 Creating Packages
  12. Object-Oriented Programming (OOP)
    1. 11.1 Classes & Objects
    2. 11.2 Constructors (init)
    3. 11.3 Encapsulation (private variables)
    4. 11.4 Inheritance
    5. 11.5 Polymorphism
    6. 11.6 Abstraction (abstract classes)
  13. File Handling
    1. 12.1 Reading Files
    2. 12.2 Writing Files
    3. 12.3 File Modes
    4. 12.4 Working with CSV Files
    5. 12.5 Working with JSON Files
  14. Error Handling (Exceptions)
    1. 13.1 try, except
    2. 13.2 finally
    3. 13.3 raise
    4. 13.4 Custom Exceptions
  15. Advanced Python
    1. 14.1 Decorators
    2. 14.2 Generators (yield)
    3. 14.3 Iterators
    4. 14.4 Context Managers
    5. 14.5 Multithreading
    6. 14.6 Multiprocessing
    7. 14.7 Async Programming (asyncio)
  16. Data Structures & Algorithms in Python
    1. 15.1 Complexity Analysis (Time, Space, Big-O)
    2. 15.2 Stack (LIFO)
    3. 15.3 Queue (FIFO)
    4. 15.4 Linked List
    5. 15.5 Trees
    6. 15.6 Graphs
    7. 15.7 Sorting Algorithms (Bubble, Merge, Quick)
    8. 15.8 Searching Algorithms (Linear, Binary)
  17. Python Ecosystem
    1. 16.1 Database Programming (SQLite, MySQL, PostgreSQL)
    2. 16.2 Web Development (Flask, Django, FastAPI)
    3. 16.3 Data Science (NumPy, Pandas, Matplotlib)
    4. 16.4 Machine Learning (Scikit-Learn, TensorFlow, PyTorch)
  18. Real-World Projects
    1. 17.1 Beginner Projects (Calculator, Password Generator, To-Do List)
      1. Calculator
      2. Password Generator
      3. To-Do List
    2. 17.2 Intermediate Projects (REST API, Web Scraper, Chat Application)
      1. REST API (using Flask)
    3. 17.3 Advanced Projects (AI Chatbot, Recommendation System)
      1. AI Chatbot
      2. Recommendation System
  19. Testing & Professional Development
    1. 18.1 Unit Testing (unittest, pytest)
    2. 18.2 Packaging & Deployment (pip, virtualenv, Docker)
    3. 18.3 Open Source Contribution (GitHub workflow, pull requests)
  20. Career Readiness
    1. 19.1 Portfolio Development
    2. Usage
    3. Demo
    4. Contact
  21. Portfolio organization
    1. 19.3 Resume Building
    2. 19.4 Freelancing and Employment

Learn Python with AI Made Easy

Understanding Python and Its Industry Importance

Why Python Matters in Industry

Python has become one of the most dominant programming languages in the world due to its simplicity, versatility, and extensive ecosystem. It powers everything from web applications to artificial intelligence systems. Companies like Google, Facebook, Netflix, and NASA use Python for critical infrastructure. Its readability makes it an excellent first language, while its power makes it suitable for professional development.

Career Opportunities

Python supports a wide range of career opportunities. Data Scientists use it for data analysis and machine learning, while Web Developers build backend applications with frameworks such as Django and Flask. Automation Engineers create scripts that streamline repetitive tasks, and DevOps Engineers use Python for infrastructure and deployment automation. AI/ML Engineers develop intelligent models and applications, while Cybersecurity Professionals use Python to build security tools and automate security-related tasks.

Technical Prerequisites

Before starting Python, you need basic computer literacy including file management, folder navigation, and using a text editor. No prior programming experience is required. Familiarity with command line operations is helpful but not necessary.

Common Beginner Mistakes

New Python developers often make mistakes like forgetting colons at the end of control statements, mixing tabs and spaces for indentation, using mutable default arguments, and not handling exceptions properly. Understanding these pitfalls early helps build better coding habits.

Role of AI in Python Development

AI tools like GitHub Copilot and ChatGPT have revolutionized Python development. They can generate code, explain concepts, debug errors, and suggest optimizations. These tools don’t replace learning but accelerate understanding and productivity.

How to Use This Course Effectively

This course combines structured learning with AI assistance. Read each concept, practice with code examples, then use AI tools to deepen understanding. Run every code example yourself and experiment with modifications. Use ChatGPT to explain concepts you find challenging.

Real-World Python Project Categories

Python projects span multiple domains. Beginner projects include calculators and to-do lists. Intermediate projects include REST APIs and web scrapers. Advanced projects include AI chatbots and recommendation systems. Each category builds specific skills.

Learning Roadmap

The roadmap progresses from basics to advanced topics. Start with environment setup and fundamental syntax. Progress through data types, control flow, and functions. Move to data structures, OOP, and file handling. Finally explore advanced features and ecosystem tools.

How to Use This Roadmap

Each section builds on previous knowledge. Master each concept before moving forward. Use the AI prompts to get additional explanations and examples. Complete the exercises and projects to reinforce learning.

Introduction

1.1 What is Python

Python is a high-level, interpreted, general-purpose programming language designed with an emphasis on simplicity, readability, and ease of development. It emphasizes code readability through significant indentation and clear syntax.

Python was designed to be easy to learn and use while being powerful enough for professional development.Python supports multiple programming paradigms, including procedural, object-oriented, and functional programming, allowing developers to choose different approaches based on the problem they are solving. Python’s philosophy emphasizes “there should be one– and preferably only one –obvious way to do it.”

Code Example

# Single-line comment
print("Hello, World!")  # This is a simple print statement

"""
This is a multi-line comment
using triple quotes
It can span multiple lines
"""

1.2 History of Python

Python was created by Guido van Rossum and first released publicly in 1991.Its name comes from Monty Python’s Flying Circus, not the snake.

Python 2.0 was released in 2000 with new features like list comprehensions and garbage collection. Python 3.0 was released in 2008 with major improvements but breaking backward compatibility. Python 3 continues to evolve with regular updates adding features like pattern matching and improved performance.

Python’s popularity stems from its simplicity, powerful libraries, and broad application range.

Python’s clean syntax makes it accessible to beginners. Its extensive standard library provides solutions for common tasks. The Python Package Index (PyPI) hosts thousands of third-party packages. Python’s versatility allows it to be used for web development, data science, automation, and more.

1.4 Key Features of Python

Python features include dynamic typing, automatic memory management, and extensive standard libraries.

Dynamic typing means variables don’t require type declarations. Automatic memory management handles allocation and garbage collection. Python is commonly described as an interpreted language, meaning its code is executed by the Python runtime rather than being compiled directly into native machine code first. Its cross-platform nature allows Python programs to run on operating systems such as Windows, macOS, and Linux, with little or no modification. Extensive libraries provide ready-to-use functionality.

Code Example

# Dynamic typing - variables can change type
bird_count = 10          # int
bird_count = "ten"       # now string

# Automatic memory management
# Python handles memory allocation and deallocation automatically

1.5 Python vs Other Languages (C, C++, Java)

Python differs from compiled languages like C, C++, and Java in execution model, syntax complexity, and use cases.

vs C/C++: Python is interpreted while C/C++ are compiled. Python has automatic memory management while C/C++ require manual memory management. Python syntax is simpler and more readable. vs Java: Python is dynamically typed while Java is statically typed. Python code is often more concise than equivalent Java code, largely because Python has simpler syntax and requires less boilerplate. Python requires less boilerplate code.

1.6 Python Applications (Web, Data Science, AI, Automation, Cybersecurity)

Python is used across multiple domains for various applications.

Web Development: Django, Flask, and FastAPI for building web applications. Data Science: NumPy, Pandas, Matplotlib for analysis and visualization. AI/ML: Python provides powerful libraries such as TensorFlow, PyTorch, and Scikit-learn for building, training, and deploying machine learning and artificial intelligence models.Automation: Scripts for file operations, web scraping, and task scheduling. Cybersecurity: Security tools, penetration testing, and vulnerability scanning.

Detailed Setup and First Application

2.1 Prerequisites for Environment Setup

Before installing Python, ensure your system meets basic requirements.

You need a computer running Windows, macOS, or Linux. Administrative privileges may be required for installation. An internet connection for downloading Python and extensions. A text editor or IDE (Integrated Development Environment) is used to write, edit, and manage source code. An IDE may also provide features such as debugging, code completion, and project management.

2.2 Setup on Linux (Ubuntu) – Command-Line Environment

Step 1: Update System Packages

sudo apt update
sudo apt upgrade

Step 2: Install Python 3

sudo apt install python3 python3-pip

Step 3: Verify Installation

python3 --version

Step 4: Create a Project Folder

mkdir python-project
cd python-project

Step 5: Create Your First Python Script

nano hello.py

Step 6: Save and Exit

print("Hello, World!")

Press Ctrl+X, then Y, then Enter.

Step 7: Run the Program

python3 hello.py

Step 8 (Optional): Make the Script Executable

chmod +x hello.py
./hello.py

2.3 Setup on Linux – Professional IDE Environment

Step 1: Install VS Code

sudo snap install --classic code

Step 2: Install the Python Extension

Open VS Code, select the Extensions panel using Ctrl+Shift+X, search for “Python”, and install the official Microsoft Python extension.

Step 3: Open Your Project Folder

code .

Step 4: Create a New Python File

Click the New File icon and name it hello.py.

Step 5: Write Your Code

print("Hello, World!")

Step 6: Run the Code

Press Ctrl+F5 to run without debugging.

Step 7: Debug Your Code

Set a breakpoint by clicking the left margin, then press F5.

2.4 Setup on Linux – AI-Integrated Development Workflow

Step 1: Create Required Accounts

Create a GitHub account and subscribe to Copilot. Create an OpenAI or ChatGPT account.

Step 2: Install GitHub Copilot Extension in VS Code

Go to Extensions, search “GitHub Copilot”, and install.

Step 3: Authenticate Copilot

Sign in with your GitHub account.

Step 4: Generate Code Using Copilot

# Type a comment and let Copilot generate the code
# Function to count birds in a list

Step 5: Use ChatGPT for Explanations and Debugging

Ask ChatGPT to explain code or fix errors.

Step 6: Refactor Code with AI

Use AI suggestions to improve code quality.

2.5 Setup on Windows – Command-Line Environment

Step 1: Download Python Installer

Visit python.org and download the latest Python installer.

Step 2: Run the Installer

Check “Add Python to PATH” and click “Install Now”.

Step 3: Verify Installation

python --version

Step 4: Create a Project Folder

mkdir python-project
cd python-project

Step 5: Create Your First Python Script

echo print("Hello, World!") > hello.py

Step 6: Run the Program

python hello.py

Step 7 (Optional): Use Python Interactive Mode

python
>>> print("Hello")
>>> exit()

2.6 Setup on Windows – Professional IDE Environment

Step 1: Install Visual Studio Code

Download from code.visualstudio.com and install.

Step 2: Install the Python Extension

Open VS Code, go to Extensions, search “Python”, and install.

Step 3: Open Your Project Folder

File → Open Folder → Select python-project.

Step 4: Create a Python File

Click New File, name it hello.py.

Step 5: Run the Code

print("Hello, World!")

Press Ctrl+F5.

Step 6: Set Up Debugging

Click the Run and Debug icon, select Python, set breakpoints, and press F5.

2.7 Setup on Windows – AI-Integrated Development Workflow

Step 1: Install GitHub Copilot Extension

Search and install “GitHub Copilot” from Extensions.

Step 2: Sign In to GitHub

Sign in when prompted.

Step 3: Generate Code with Copilot

# Function to calculate total birds

Step 4: Use ChatGPT for Debugging

Copy error messages and ask ChatGPT for solutions.

Step 5: Refactor with AI

Ask AI to suggest improvements for your code.

2.8 Setup on macOS – Command-Line Environment

Step 1: Install Homebrew (Package Manager)

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Step 2: Install Python 3 via Homebrew

brew install python

Step 3: Verify Installation

python3 --version

Step 4: Create a Project Folder

mkdir python-project
cd python-project

Step 5: Create a Python Script

echo 'print("Hello, World!")' > hello.py

Step 6: Run the Program

python3 hello.py

2.9 Setup on macOS – Professional IDE Environment

Step 1: Install VS Code

Download from code.visualstudio.com and drag to Applications.

Step 2: Install Python Extension

Open VS Code, go to Extensions, search “Python”, and install.

Step 3: Open Project Folder

code .

Step 4: Create and Run hello.py

Create hello.py with print("Hello, World!") and press Ctrl+F5.

Step 5: Use the Integrated Debugger

Set breakpoints and press F5.

2.10 Setup on macOS – AI-Integrated Development Workflow

Step 1: Install GitHub Copilot in VS Code

Install from Extensions.

Step 2: Generate Code with AI

# Create a bird class with species and count

Step 3: Use ChatGPT to Understand Code

Ask ChatGPT to explain generated code.

Step 4: Optimize with AI

Request AI to suggest performance improvements.

2.11 Python Interpreter and Running Programs

The Python interpreter executes Python code either interactively or from files.

Interactive mode runs commands one at a time. Script mode executes complete files. The interpreter handles compilation to bytecode and execution. REPL (Read-Eval-Print-Loop) allows immediate feedback.

Code Example

# In interactive mode
>>> print("Hello")
Hello
>>> 2 + 3
5

# In script mode
# File: script.py
print("Hello from script")

2.12 First Python Program (Hello World)

Code Example

# hello.py - Your first Python program
print("Hello, World!")

# Using variables
message = "Hello, Python!"
print(message)

# Multiple prints
print("This is", "a", "single", "line")

# Formatted output
name = "Python"
version = 3.12
print(f"Welcome to {name} {version}!")

The print() function outputs text to the console. Variables store values for later use. The f-string format embeds variables in strings.

2.13 Software Execution Lifecycle

Python programs go through multiple stages from writing to execution.

Writing: Create source code in .py files. Compilation: Python compiles to bytecode (.pyc files). Execution: The Python Virtual Machine executes bytecode. Error Handling: Runtime errors are caught and managed. Output: Results are displayed or saved.

Code Example

# Understanding the execution flow
def main():
    print("Program starts")
    try:
        result = 10 / 2
        print(f"Result: {result}")
    except Exception as e:
        print(f"Error: {e}")
    print("Program ends")

if __name__ == "__main__":
    main()

AI Integration with AI Tools

3.1 ChatGPT Workflows

ChatGPT helps with learning, debugging, and generating Python code.

Use ChatGPT to explain concepts, debug errors, generate examples, optimize code, and suggest solutions. Provide clear context and specific questions.

Code Example

# Ask ChatGPT to explain this code
bird_data = {
    'Sparrow': 10,
    'Eagle': 3,
    'Hawk': 5
}

# ChatGPT can explain dictionary comprehension
total = sum(count for count in bird_data.values())

3.2 GitHub Copilot

Copilot is an AI pair programmer that suggests code in real-time.

Copilot uses machine learning to predict and suggest code. It understands context from comments and surrounding code. It can generate complete functions, suggest alternatives, and speed up development.

Code Example

# Type a comment and Copilot suggests code
# Function to count birds in a dictionary

# Copilot may generate:
def count_birds(bird_dict):
    return sum(bird_dict.values())

3.3 AI Debugging

AI tools help identify and fix code errors quickly.

Provide error messages to AI for explanation. AI suggests fixes and explains why errors occurred. AI can recommend debugging techniques and best practices.

Code Example

# Error example
try:
    print(bird_count)  # NameError
except NameError as e:
    print(f"Error: {e}")
    # AI suggests: define bird_count before using it

3.4 AI Testing

AI can generate test cases and suggest testing strategies.

AI creates test cases for functions. AI suggests edge cases and boundary conditions. AI recommends testing frameworks and approaches.

Code Example

# Ask AI to generate test for this function
def add_birds(birds, count):
    return birds + count

# AI might suggest:
def test_add_birds():
    assert add_birds(10, 5) == 15
    assert add_birds(0, 0) == 0

3.5 AI Refactoring

AI suggests code improvements and refactoring opportunities.

AI identifies code smells and suggests better patterns. AI recommends performance optimizations. AI suggests more Pythonic approaches.

Code Example

# Before refactoring
def get_bird_count(data):
    total = 0
    for key in data:
        total = total + data[key]
    return total

# After AI suggests:
def get_bird_count(data):
    return sum(data.values())

Python Programming Basics

4.1 Python Syntax Rules

Python syntax defines the rules for writing valid Python code.

Python uses line breaks to separate statements. Indentation determines code blocks. Colons start blocks. Comments start with #. Python is case-sensitive.

Code Example

# Python syntax rules in action
# Indentation is crucial
def display_birds():
    print("Sparrow: 10")  # This is indented
    print("Eagle: 3")     # Same indentation level
    if True:
        print("Inside if")  # Increased indentation

# Case sensitivity
bird = "Sparrow"
Bird = "Eagle"  # Different variable
print(bird)     # Sparrow
print(Bird)     # Eagle

4.2 Indentation in Python

Indentation defines code blocks and structure in Python.

Python uses whitespace to indicate code grouping. 4 spaces is the standard. Tabs and spaces cannot be mixed. Consistent indentation is required. Incorrect indentation causes IndentationError.

Code Example

# Correct indentation
def count_birds():
    print("Counting birds")  # 4 spaces indentation
    if True:
        print("Inside if")   # 8 spaces
    return 0

# Incorrect indentation (will cause error)
# def bad_function():
# print("No indentation")  # IndentationError

# Proper block indentation
for i in range(3):
    print(f"Bird {i}")     # Indented inside loop
print("Done")              # Not indented - outside loop

4.3 Variables

Variables store data values that can change during program execution.

Variables are created when assigned. Python is dynamically typed. Variable names in Python must begin with a letter or an underscore (_). They cannot start with a number. Multiple assignments are allowed. Variables can be deleted with del.

Code Example

# Variable assignment
bird_count = 10          # Integer
bird_name = "Sparrow"    # String
bird_weight = 25.5       # Float
is_migratory = True      # Boolean

# Multiple assignment
eagle, hawk = 3, 5       # eagle=3, hawk=5

# Dynamic typing - variable can change type
bird = 10               # int
bird = "Ten"            # str (now string)

# Deleting variables
del bird_count
# print(bird_count)     # NameError - variable deleted

4.4 Naming Conventions

Naming conventions are rules for naming variables, functions, and classes.

snake_case for variables and functions. PascalCase for classes. ALL_CAPS for constants. Names should be descriptive. Avoid single letters except for loops. Avoid reserved keywords.

Code Example

# Variable naming conventions
bird_count = 10          # snake_case
BIRD_LIMIT = 100         # ALL_CAPS (constant)

# Function naming
def count_birds():       # snake_case
    pass

# Class naming
class BirdCount:         # PascalCase
    pass

# Avoid reserved words as names
# class = "Bird"         # ERROR: class is reserved

# Good descriptive names
sparrow_total = 10
eagle_population = 3

# Bad naming
# x = 10                 # Not descriptive
# total = 10             # Not specific enough

4.5 Python Keywords

Keywords are reserved words with special meaning in Python.

Keywords include if, else, for, while, def, class, return, import, try, except, True, False, None. Keywords cannot be used as variable names. Python has 35 keywords.

Code Example

# Python keywords in action
# Keywords are highlighted in code editors

if True:                 # 'if' is a keyword
    print("Yes")
else:                    # 'else' is a keyword
    print("No")

def add(a, b):          # 'def' is a keyword
    return a + b        # 'return' is a keyword

class Bird:             # 'class' is a keyword
    pass

# Cannot use keywords as variable names
# return = 10           # SyntaxError
# class = "Bird"        # SyntaxError

4.6 Type Casting

Type casting converts one data type to another.

Implicit casting occurs automatically. Explicit casting is done manually using functions. Common casting functions include int(), float(), str(), list(), tuple(), dict(), set().

Code Example

# Implicit casting (automatic)
result = 10 + 5.5       # int + float = float
print(type(result))     # <class 'float'>

# Explicit casting (manual)
num_str = "10"
num_int = int(num_str)  # Convert string to int
num_float = float(num_str)  # Convert string to float

# Complex conversions
number = 123
text = str(number)      # int to string
print(type(text))       # <class 'str'>

# List to tuple
bird_list = ["Sparrow", "Eagle"]
bird_tuple = tuple(bird_list)
print(type(bird_tuple)) # <class 'tuple'>

# Tuple to list
bird_tuple = ("Sparrow", "Eagle")
bird_list = list(bird_tuple)
print(type(bird_list))  # <class 'list'>

Data Types & Operators

5.1 Numeric Types (int, float, complex)

Numeric types handle numbers of different kinds in Python.

int: Whole numbers without decimal points. float: Numbers with decimal points. complex: Numbers with real and imaginary parts. Python supports large integers and scientific notation.

Code Example

# Integer (int)
sparrow_count = 10
eagle_count = -3
big_number = 1_000_000  # Underscores for readability

# Float (float)
bird_weight = 25.5
eagle_weight = 4.5
scientific = 1.2e-3     # 0.0012

# Complex (complex)
bird_complex = 3 + 4j
print(bird_complex.real)  # 3.0
print(bird_complex.imag)  # 4.0

# Type checking
print(type(10))          # <class 'int'>
print(type(10.5))        # <class 'float'>
print(type(3+4j))        # <class 'complex'>

5.2 Boolean Type

Boolean represents truth values in Python.

Boolean has two values: True and False. Boolean values are case-sensitive. True is 1 and False is 0 when used in arithmetic. Used for conditions and comparisons.

Code Example

# Boolean values
is_migrating = True
is_endangered = False

# Boolean from comparisons
has_birds = 10 > 5      # True
has_eagles = 3 > 5      # False

# Boolean arithmetic
print(True + True)      # 2
print(True * 5)         # 5
print(False + 3)        # 3

# Boolean conditions
bird_count = 10
if bird_count > 5:      # Condition evaluates to True
    print("Many birds")

# Type checking
print(type(True))       # <class 'bool'>
print(type(False))      # <class 'bool'>

5.3 Sequence Types (string, list, tuple, range)

String

Strings are sequences of characters enclosed in quotes.

Strings can be enclosed in single, double, or triple quotes. Strings are immutable. They support indexing, slicing, and many methods. Unicode characters are supported.

Code Example

# String creation
bird1 = "Sparrow"           # Double quotes
bird2 = 'Eagle'             # Single quotes
bird3 = """Multi-line
bird
text"""                     # Triple quotes

# String indexing
bird = "Sparrow"
print(bird[0])              # 'S'
print(bird[-1])             # 'w'

# String slicing
print(bird[1:4])            # 'par'
print(bird[:3])             # 'Spa'
print(bird[3:])             # 'arrow'

# String immutability (cannot change)
# bird[0] = 'P'            # ERROR: string immutable
bird = "P" + bird[1:]       # Create new string

List

Lists are ordered, mutable sequences of elements.

Lists can contain mixed types. Lists are mutable (can change). Support indexing, slicing, and many methods. Lists are dynamic and can grow/shrink.

Code Example

# List creation
birds = ["Sparrow", "Eagle", "Hawk", "Cardinal"]
mixed = [1, "Bird", 3.14, True]

# List indexing
print(birds[0])             # Sparrow
print(birds[-1])            # Cardinal

# List slicing
print(birds[1:3])           # ['Eagle', 'Hawk']
print(birds[:2])            # ['Sparrow', 'Eagle']

# List methods
birds.append("Finch")       # Add to end
birds.insert(1, "Robin")    # Insert at position
birds.remove("Eagle")       # Remove specific element
birds.pop()                 # Remove and return last element
birds.pop(1)                # Remove at position

# List comprehensions
numbers = [1, 2, 3, 4]
squares = [x**2 for x in numbers]  # [1, 4, 9, 16]

Tuple

Tuples are ordered, immutable sequences of elements.

Tuples are similar to lists but cannot be changed after creation. They are faster than lists and can be used as dictionary keys. Single element tuples need a comma.

Code Example

# Tuple creation
birds = ("Sparrow", "Eagle", "Hawk")
single_bird = ("Sparrow",)    # Tuple with one element

# Accessing tuples
print(birds[0])             # Sparrow
print(birds[-1])            # Hawk
print(birds[1:3])           # ('Eagle', 'Hawk')

# Tuple immutability (cannot change)
# birds[0] = "Robin"        # ERROR: tuple immutable

# Converting tuple to list (to modify)
bird_list = list(birds)     # ['Sparrow', 'Eagle', 'Hawk']
bird_list.append("Robin")
birds = tuple(bird_list)    # ('Sparrow', 'Eagle', 'Hawk', 'Robin')

# Tuple unpacking
a, b, c = birds[:3]
print(a, b, c)              # Sparrow Eagle Hawk

Range

Range generates a sequence of numbers.

Range is used for iterating a specific number of times. It can have start, stop, and step parameters. Range is memory-efficient as it generates numbers on demand.

Code Example

# Range with one parameter (stop)
for i in range(5):
    print(i)                # 0, 1, 2, 3, 4

# Range with two parameters (start, stop)
for i in range(2, 6):
    print(i)                # 2, 3, 4, 5

# Range with three parameters (start, stop, step)
for i in range(0, 10, 2):
    print(i)                # 0, 2, 4, 6, 8

# Converting range to list
numbers = list(range(5))    # [0, 1, 2, 3, 4]

5.4 Set Types

Sets are unordered collections of unique elements.

Sets contain unique elements only. Sets are unordered (no indexing). Sets are mutable (can add/remove). Sets support mathematical operations like union and intersection. Sets cannot contain other sets (nested sets not allowed).

Code Example

# Set creation
birds = {"Sparrow", "Eagle", "Hawk", "Sparrow"}  # {'Eagle', 'Hawk', 'Sparrow'}

# Using set constructor
birds = set(["Sparrow", "Eagle", "Hawk"])

# Adding elements
birds.add("Cardinal")
birds.update(["Robin", "Finch"])

# Removing elements
birds.remove("Eagle")       # Raises error if not found
birds.discard("Parrot")     # No error if not found
birds.pop()                 # Remove arbitrary element

# Set operations
set1 = {"Sparrow", "Eagle", "Hawk"}
set2 = {"Sparrow", "Robin", "Finch"}

print(set1.union(set2))          # Union
print(set1.intersection(set2))   # Intersection
print(set1.difference(set2))     # Difference
print(set1.symmetric_difference(set2))  # Symmetric difference

5.5 Mapping Type – Dictionary

Dictionaries store key-value pairs.

Dictionaries use keys to access values. Keys must be immutable (strings, numbers, tuples). Values can be any type. Duplicate keys take the last value. Dictionaries are unordered (Python 3.7+ preserves insertion order).

Code Example

# Dictionary creation
birds = {
    "Sparrow": 10,
    "Eagle": 3,
    "Hawk": 5
}

# Using dict constructor
birds = dict(Sparrow=10, Eagle=3, Hawk=5)

# Accessing values
print(birds["Sparrow"])     # 10
print(birds.get("Eagle"))   # 3
print(birds.get("Finch"))   # None (no error)

# Adding/updating
birds["Robin"] = 8          # Add new
birds["Eagle"] = 4          # Update existing

# Duplicate key - last value taken
birds = {"Sparrow": 10, "Sparrow": 20}  # {'Sparrow': 20}

# Dictionary methods
keys = birds.keys()         # View keys
values = birds.values()     # View values
items = birds.items()       # View key-value pairs

# Nested dictionaries
bird_data = {
    "Sparrow": {"count": 10, "weight": 25.5},
    "Eagle": {"count": 3, "weight": 4500.0}
}
print(bird_data["Sparrow"]["count"])  # 10

5.6 Arithmetic Operators

Arithmetic operators perform mathematical calculations.

Operators include + (addition), - (subtraction), * (multiplication), / (division), // (floor division), % (modulo), ** (exponentiation).

Code Example

# Arithmetic operators
x = 10
y = 3

print(x + y)      # 13
print(x - y)      # 7
print(x * y)      # 30
print(x / y)      # 3.3333333333333335
print(x // y)     # 3 (floor division)
print(x % y)      # 1 (modulo/remainder)
print(x ** y)     # 1000 (exponentiation)

# Order of operations
result = 2 + 3 * 4        # 14 (multiplication first)
result = (2 + 3) * 4      # 20 (parentheses first)

5.7 Comparison Operators

Comparison operators compare values and return Boolean results.

Operators include == (equal), != (not equal), > (greater than), < (less than), >= (greater or equal), <= (less or equal).

Code Example

a = 10
b = 20
c = 10

# Equal and not equal
print(a == b)     # False
print(a == c)     # True
print(a != b)     # True

# Comparison
print(a > b)      # False
print(a < b)      # True
print(a >= c)     # True
print(a <= b)     # True

# String comparison
print("Sparrow" == "Sparrow")    # True
print("Eagle" > "Hawk")          # False (alphabetical)

5.8 Logical Operators

Logical operators combine boolean expressions.

Operators include and (both true), or (at least one true), not (invert). Short-circuit evaluation stops at first determining value.

Code Example

x = True
y = False
z = True

# Logical operations
print(x and y)    # False
print(x and z)    # True
print(x or y)     # True
print(y or x)     # True
print(not x)      # False
print(not y)      # True

# Short-circuit evaluation
def expensive_check():
    print("Checking...")
    return True

# and stops at first False
result = False and expensive_check()  # expensive_check not called

# or stops at first True
result = True or expensive_check()    # expensive_check not called

5.9 Assignment Operators

Assignment operators assign values and combine operations.

Operators include = (assign), +=, -=, *=, /=, //=, %=, **=. They perform operation and assignment in one step.

Code Example

# Basic assignment
birds = 10

# Compound assignment
birds += 5          # birds = birds + 5 = 15
birds -= 3          # birds = birds - 3 = 12
birds *= 2          # birds = birds * 2 = 24
birds /= 4          # birds = birds / 4 = 6.0
birds //= 2         # birds = birds // 2 = 3
birds %= 2          # birds = birds % 2 = 1
birds **= 3         # birds = birds ** 3 = 1

# Practical example
count = 10
count += 5
print(count)        # 15

5.10 Bitwise Operators

Bitwise operators work on binary representations of integers.

Operators include & (AND), | (OR), ^ (XOR), ~ (NOT), << (left shift), >> (right shift). Used for low-level programming and flag manipulation.

Code Example

a = 5       # 0101 in binary
b = 3       # 0011 in binary

print(a & b)     # 1 (0001)
print(a | b)     # 7 (0111)
print(a ^ b)     # 6 (0110)
print(~a)        # -6 (two's complement)
print(a << 1)    # 10 (1010)
print(a >> 1)    # 2 (0010)

# Flags example
READ = 1    # 001
WRITE = 2   # 010
EXEC = 4    # 100

# Set read and write permission
permission = READ | WRITE  # 3 (011)
print(permission & READ)   # 1 (True)
print(permission & EXEC)   # 0 (False)

5.11 Identity Operators

Identity operators check if two variables refer to the same object.

Operators include is and is not. They compare object identity, not value equality. is checks if two variables reference the same object in memory.

Code Example

a = [1, 2, 3]
b = [1, 2, 3]
c = a

# Identity vs equality
print(a == b)     # True (same values)
print(a is b)     # False (different objects)
print(a is c)     # True (same object)

# With integers (Python caches small integers)
x = 10
y = 10
print(x is y)     # True (small integers cached)

x = 1000
y = 1000
print(x is y)     # False (larger integers not cached)

# None is a singleton
result = None
print(result is None)   # True
print(result is not None)  # False

5.12 Membership Operators

Membership operators check if an element exists in a sequence.

Operators include in and not in. Work with strings, lists, tuples, dictionaries (checks keys), and sets.

Code Example

# String membership
text = "Sparrow"
print('S' in text)        # True
print('x' in text)        # False
print('ar' in text)       # True

# List membership
birds = ["Sparrow", "Eagle", "Hawk"]
print("Eagle" in birds)    # True
print("Finch" in birds)    # False

# Dictionary membership (checks keys)
data = {"Sparrow": 10, "Eagle": 3}
print("Sparrow" in data)    # True
print(10 in data)           # False (values not checked)

# Not in
print("Finch" not in birds)  # True

Control Flow

6.1 Conditional Statements (if, if-else, elif ladder, nested conditions)

Conditional statements control program flow based on conditions.

The if statement executes a block of code when its specified condition evaluates to True. if-else provides an alternate block. elif (else-if) checks multiple conditions. Nested conditions place conditional blocks inside others.

Code Example

# Simple if
bird_count = 10
if bird_count > 5:
    print("Many birds")

# if-else
if bird_count > 5:
    print("Many birds")
else:
    print("Few birds")

# elif ladder
if bird_count > 10:
    print("Large flock")
elif bird_count > 5:
    print("Medium flock")
elif bird_count > 0:
    print("Small flock")
else:
    print("No birds")

# Nested conditions
if bird_count > 0:
    print("Birds present")
    if bird_count > 10:
        print("Large flock")
    else:
        print("Small flock")
else:
    print("No birds present")

# Multiple conditions with logical operators
if bird_count > 0 and bird_count < 20:
    print("Moderate flock")

6.2 Loops (for, while)

For Loop

The for loop iterates over sequences.

for loop works with iterables like strings, lists, tuples, dictionaries, and ranges. It’s used when the number of iterations is known or when iterating over a collection.

Code Example

# Iterating over a string
for bird in "Eagle":
    print(bird)

# Iterating over a list
birds = ["Sparrow", "Eagle", "Hawk"]
for bird in birds:
    print(bird)

# Iterating over a dictionary
bird_data = {"Sparrow": 10, "Eagle": 3}
for species, count in bird_data.items():
    print(f"{species}: {count}")

# Using range
for i in range(5):
    print(i)                # 0, 1, 2, 3, 4

for i in range(2, 6):       # 2, 3, 4, 5
    print(i)

for i in range(0, 10, 2):   # 0, 2, 4, 6, 8
    print(i)

While Loop

The while loop repeats while a condition is true.

while loop continues until the condition becomes false. It’s used when the number of iterations is unknown or depends on a condition that changes during execution.

Code Example

# Basic while loop
count = 0
while count < 5:
    print(count)
    count += 1

# While with user input
birds = []
while True:
    species = input("Enter bird (or 'done' to stop): ")
    if species.lower() == "done":
        break
    birds.append(species)

# While with condition
bird_count = 10
while bird_count > 0:
    print(bird_count)
    bird_count -= 1

# Infinite loop (use carefully)
# while True:
#     print("This runs forever")

6.3 Loop Control Statements (break, continue, pass)

Loop control statements modify loop execution flow.

break exits the loop entirely. continue skips current iteration and continues with next. pass does nothing (placeholder for code).

Code Example

# Break - exits loop early
birds = ["Sparrow", "Eagle", "Hawk", "Robin", "Finch"]
for bird in birds:
    if bird == "Hawk":
        break
    print(bird)        # Sparrow, Eagle (stops before Hawk)

# Continue - skips current iteration
for bird in birds:
    if bird == "Hawk":
        continue
    print(bird)        # Sparrow, Eagle, Robin, Finch (skips Hawk)

# Pass - placeholder (does nothing)
for bird in birds:
    if bird == "Eagle":
        pass           # TODO: Add special handling for Eagles
    else:
        print(bird)

# Nested loops with break
for i in range(3):
    for j in range(3):
        if i == 1 and j == 1:
            break      # Breaks only inner loop
        print(i, j)

# Practical example - find first occurrence
target = "Hawk"
for bird in birds:
    if bird == target:
        print(f"Found {target}")
        break
else:
    print(f"{target} not found")

Functions & Modular Programming

7.1 Defining Functions

Functions are reusable blocks of code that perform specific tasks.

Functions help organize code, avoid repetition, and enable reusability. They can take parameters and return values. Functions are defined with def keyword.

Code Example

# Simple function
def display_birds():
    print("Sparrow")
    print("Eagle")
    print("Hawk")

# Call the function
display_birds()

# Function with parameters
def count_birds(sparrow_count, eagle_count):
    total = sparrow_count + eagle_count
    print(f"Total: {total}")

count_birds(10, 3)

# Function with return value
def get_total_birds(sparrow_count, eagle_count):
    return sparrow_count + eagle_count

total = get_total_birds(10, 3)
print(total)

# Function with multiple returns
def get_bird_stats(sparrow_count, eagle_count):
    total = sparrow_count + eagle_count
    ratio = sparrow_count / eagle_count if eagle_count > 0 else 0
    return total, ratio

total, ratio = get_bird_stats(10, 3)

7.2 Parameters & Arguments

Positional Arguments

Positional arguments are matched by position in function call.

Values are assigned to parameters in the order they’re passed. Required unless default values are provided.

Code Example

def bird_info(species, count, weight):
    print(f"{species}: {count} birds, {weight}g")

# Positional arguments
bird_info("Sparrow", 10, 25.5)  # Sparrow, 10, 25.5

Keyword Arguments

Keyword arguments are passed with parameter names.

Keyword arguments can be in any order. They make code more readable. Cannot follow positional arguments.

Code Example

def bird_info(species, count, weight):
    print(f"{species}: {count} birds, {weight}g")

# Keyword arguments
bird_info(species="Sparrow", count=10, weight=25.5)
bird_info(count=10, weight=25.5, species="Sparrow")

# Mixing positional and keyword
bird_info("Sparrow", count=10, weight=25.5)  # Valid
# bird_info(species="Sparrow", 10, 25.5)    # Invalid

Default Arguments

Default arguments provide values when not specified.

Default values are assigned to parameters. Must be rightmost parameters. Evaluated once at function definition.

Code Example

def bird_info(species, count=0, weight=0.0):
    print(f"{species}: {count} birds, {weight}g")

# Using defaults
bird_info("Sparrow")                    # count=0, weight=0.0
bird_info("Eagle", 3)                   # weight=0.0
bird_info("Hawk", 5, 1200.0)            # All provided

# Default with list (caution)
def add_bird(bird, birds_list=[]):      # Mutable default
    birds_list.append(bird)
    return birds_list

# This shares the default list across calls
print(add_bird("Sparrow"))              # ['Sparrow']
print(add_bird("Eagle"))                # ['Sparrow', 'Eagle']

# Better approach
def add_bird(bird, birds_list=None):
    if birds_list is None:
        birds_list = []
    birds_list.append(bird)
    return birds_list

Variable-Length Arguments

Variable-length arguments accept any number of arguments.

*args for positional arguments (tuple). **kwargs for keyword arguments (dictionary).

Code Example

# Variable positional arguments
def count_birds(*birds):
    print(f"Birds seen: {len(birds)}")
    for bird in birds:
        print(bird)

count_birds("Sparrow", "Eagle")        # 2 birds
count_birds("Sparrow", "Eagle", "Hawk") # 3 birds

# Variable keyword arguments
def display_bird_info(**bird_data):
    for key, value in bird_data.items():
        print(f"{key}: {value}")

display_bird_info(species="Sparrow", count=10)
display_bird_info(species="Eagle", count=3, weight=4500.0)

# Combined
def bird_report(*species, **stats):
    print(f"Species: {species}")
    print(f"Stats: {stats}")

bird_report("Sparrow", "Eagle", total=13, average=10.5)

7.3 Return Values

Functions return values using the return statement.

return ends function execution and returns a value. Functions without return return None. Multiple values can be returned as a tuple.

Code Example

# Single return
def get_total(a, b):
    return a + b

result = get_total(10, 3)
print(result)  # 13

# Multiple returns (tuple)
def get_bird_stats(sparrow_count, eagle_count):
    total = sparrow_count + eagle_count
    ratio = sparrow_count / eagle_count if eagle_count > 0 else 0
    return total, ratio

total, ratio = get_bird_stats(10, 3)
print(total)  # 13
print(ratio)  # 3.333...

# No return (returns None)
def display_bird(bird):
    print(f"Bird: {bird}")

result = display_bird("Sparrow")
print(result)  # None

# Conditional returns
def get_bird_status(count):
    if count == 0:
        return "No birds"
    elif count > 0 and count < 10:
        return "Few birds"
    else:
        return "Many birds"

print(get_bird_status(5))   # Few birds
print(get_bird_status(0))   # No birds

7.4 Lambda Functions

Lambda functions are anonymous inline functions.

Lambda functions are defined with lambda keyword. They’re single-expression functions. Used where small functions are needed briefly.

Code Example

# Lambda function
square = lambda x: x**2
print(square(5))  # 25

# Lambda with filter
birds = ["Sparrow", "Eagle", "Hawk", "Robin"]
long_names = list(filter(lambda x: len(x) > 4, birds))
print(long_names)  # ['Sparrow', 'Robin']

# Lambda with map
bird_counts = [10, 3, 5, 8]
doubled = list(map(lambda x: x * 2, bird_counts))
print(doubled)  # [20, 6, 10, 16]

# Lambda with sorted
birds = ["Sparrow", "Eagle", "Hawk"]
sorted_birds = sorted(birds, key=lambda x: len(x))
print(sorted_birds)  # ['Eagle', 'Hawk', 'Sparrow']

# Multiple parameters
add = lambda a, b: a + b
print(add(5, 3))  # 8

# Ternary lambda
max_val = lambda a, b: a if a > b else b
print(max_val(10, 5))  # 10

7.5 Recursion

Recursion is a function calling itself to solve problems.

Recursive functions have a base case (stopping condition) and recursive case (call itself with smaller problem). Used for problems with recursive structure like tree traversal.

Code Example

# Factorial using recursion
def factorial(n):
    # Base case
    if n <= 1:
        return 1
    # Recursive case
    return n * factorial(n - 1)

print(factorial(5))  # 120
print(factorial(3))  # 6

# Fibonacci using recursion
def fibonacci(n):
    # Base cases
    if n <= 0:
        return 0
    elif n == 1:
        return 1
    # Recursive case
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(6))  # 8

# List sum using recursion
def sum_list(numbers):
    if not numbers:
        return 0
    return numbers[0] + sum_list(numbers[1:])

print(sum_list([1, 2, 3, 4]))  # 10

# Factorial with recursion depth
import sys
sys.setrecursionlimit(1000)  # Set recursion limit

# Recursion with direct calculation
def bird_count(n):
    if n == 0:
        return 0
    return 1 + bird_count(n - 1)

print(bird_count(5))  # 5

Python Data Structures

8.1 Lists

Lists are mutable, ordered collections of elements.

Lists are created with brackets []. They can hold mixed types. Lists support indexing, slicing, and many methods.

Code Example

# List creation
birds = ["Sparrow", "Eagle", "Hawk", "Cardinal"]
mixed = [1, "Bird", 3.14, True]

# List methods
birds.append("Finch")      # Add to end
birds.insert(1, "Robin")   # Insert at position
birds.extend(["Dove", "Crow"])  # Add multiple

birds.remove("Eagle")      # Remove specific
bird = birds.pop()         # Remove and return last
bird = birds.pop(1)        # Remove at position

birds.sort()               # Sort in place
birds.reverse()            # Reverse in place

# Indexing
print(birds[0])            # First
print(birds[-1])           # Last

# Slicing
print(birds[1:3])          # Elements 1-2
print(birds[:2])           # First two
print(birds[2:])           # From index 2

# List comprehension
numbers = [1, 2, 3, 4]
squares = [x**2 for x in numbers]    # [1, 4, 9, 16]
evens = [x for x in numbers if x % 2 == 0]  # [2, 4]

# Nested lists
birds_nest = [
    ["Sparrow", 10],
    ["Eagle", 3],
    ["Hawk", 5]
]
print(birds_nest[0][0])    # Sparrow

8.2 Tuples

Tuples are immutable, ordered collections of elements.

Tuples are created with parentheses (). They cannot be changed after creation. Faster than lists and can be dictionary keys.

Code Example

# Tuple creation
birds = ("Sparrow", "Eagle", "Hawk")
single = ("Sparrow",)           # Comma required for single element

# Indexing and slicing
print(birds[0])                 # Sparrow
print(birds[-1])                # Hawk
print(birds[1:3])               # ('Eagle', 'Hawk')

# Tuple operations
print(birds + ("Robin", "Finch"))  # Concatenation
print(birds * 2)                    # Repetition
print(len(birds))                   # Length

# Tuple unpacking
a, b, c = birds
print(a, b, c)                  # Sparrow Eagle Hawk

# Tuple as dictionary key
bird_data = {("Sparrow", 2024): 10, ("Eagle", 2024): 3}

# Converting tuple to list
bird_list = list(birds)         # ['Sparrow', 'Eagle', 'Hawk']
bird_list.append("Robin")
birds = tuple(bird_list)

# Tuple methods
print(birds.count("Eagle"))     # Count occurrences
print(birds.index("Eagle"))     # Find index

8.3 Sets

Sets are unordered, mutable collections of unique elements.

Sets are created with braces {}. They contain unique elements only. No indexing (unordered). Support mathematical operations.

Code Example

# Set creation
birds = {"Sparrow", "Eagle", "Hawk", "Sparrow"}  # {'Eagle', 'Hawk', 'Sparrow'}

# Using set constructor
birds = set(["Sparrow", "Eagle", "Hawk"])

# Adding and removing
birds.add("Cardinal")
birds.update(["Robin", "Finch"])
birds.remove("Eagle")      # Raises error if not found
birds.discard("Parrot")    # No error if not found

# Set operations
set1 = {"Sparrow", "Eagle", "Hawk"}
set2 = {"Sparrow", "Robin", "Finch"}

print(set1 | set2)                  # Union
print(set1 & set2)                  # Intersection
print(set1 - set2)                  # Difference
print(set1 ^ set2)                  # Symmetric difference

# Set comprehension
numbers = [1, 2, 2, 3, 3, 4]
unique = {x for x in numbers}      # {1, 2, 3, 4}

# Membership testing
print("Eagle" in birds)            # True

8.4 Dictionaries

Dictionaries store key-value pairs with fast lookup.

Dictionaries use keys to access values. Keys must be immutable. Duplicate keys take the last value. Python 3.7+ preserves insertion order.

Code Example

# Dictionary creation
birds = {
    "Sparrow": 10,
    "Eagle": 3,
    "Hawk": 5
}

# Using dict constructor
birds = dict(Sparrow=10, Eagle=3, Hawk=5)

# Accessing values
print(birds["Sparrow"])           # 10
print(birds.get("Eagle"))         # 3
print(birds.get("Finch", 0))      # 0 (default value)

# Adding and updating
birds["Robin"] = 8                # Add new
birds["Eagle"] = 4                # Update existing

# Dictionary methods
keys = birds.keys()               # dict_keys(['Sparrow', 'Eagle', ...])
values = birds.values()           # dict_values([10, 3, 5, ...])
items = birds.items()             # dict_items([('Sparrow', 10), ...])

# Deleting items
del birds["Hawk"]                 # Remove key
popped = birds.pop("Eagle")       # Remove and return
popped = birds.popitem()          # Remove last item

# Nested dictionaries
bird_data = {
    "Sparrow": {"count": 10, "weight": 25.5},
    "Eagle": {"count": 3, "weight": 4500.0}
}
print(bird_data["Sparrow"]["count"])  # 10

# Dictionary comprehension
numbers = [1, 2, 3]
squares = {x: x**2 for x in numbers}  # {1: 1, 2: 4, 3: 9}

Strings & Text Processing

9.1 String Basics (indexing, slicing)

Strings are sequences of characters with support for indexing and slicing.

Strings are immutable. Positive indexing starts at 0. Negative indexing starts at -1. Slicing creates new strings.

Code Example

# String creation with different quotes
single = 'Sparrow'
double = "Eagle"
triple = """Multi-line
bird
text"""

# Positive indexing
bird = "Sparrow"
print(bird[0])    # S
print(bird[3])    # r
print(bird[6])    # w

# Negative indexing
print(bird[-1])   # w
print(bird[-3])   # r

# Slicing
print(bird[1:4])      # par
print(bird[:3])       # Spa
print(bird[3:])       # row
print(bird[::2])      # S r w (jump by 2)
print(bird[::-1])     # worrapS (reverse)

# Check membership
print('S' in "Sparrow")     # True
print('x' in "Sparrow")     # False

# Length
print(len("Sparrow"))       # 7

9.2 String Methods (upper, lower, split, join, replace, etc.)

String methods provide common operations on strings.

Methods include upper(), lower(), split(), join(), replace(), find(), count(), strip(), etc.

Code Example

bird = "  Sparrow  "

# Case conversion
print(bird.upper())          # "  SPARROW  "
print(bird.lower())          # "  sparrow  "
print(bird.title())          # "  Sparrow  "

# Whitespace removal
print(bird.strip())          # "Sparrow"
print(bird.lstrip())         # "Sparrow  "
print(bird.rstrip())         # "  Sparrow"

# Searching
text = "The eagle soared high"
print(text.find("eagle"))    # 4 (position)
print(text.find("hawk"))     # -1 (not found)
print(text.count("e"))       # 3

# Replacement
print(text.replace("eagle", "hawk"))  # "The hawk soared high"

# Split and join
words = text.split()         # ['The', 'eagle', 'soared', 'high']
joined = "-".join(words)     # "The-eagle-soared-high"

# Checking
print("Sparrow".startswith("S"))    # True
print("Sparrow".endswith("w"))      # True
print("Sparrow".isalpha())          # True
print("123".isdigit())              # True

# Title and capitalize
print("hello world".title())        # "Hello World"
print("hello world".capitalize())   # "Hello world"

9.3 String Formatting (f-strings, format() method)

String formatting inserts values into strings.

f-strings (Python 3.6+) are the modern preferred method. .format() is the older method. % formatting is the oldest method.

Code Example

# f-strings (recommended)
species = "Sparrow"
count = 10
weight = 25.5

print(f"Bird: {species}")                     # Bird: Sparrow
print(f"Count: {count}, Weight: {weight}g")   # Count: 10, Weight: 25.5g
print(f"{species}: {count:.2f} birds")        # Sparrow: 10.00 birds

# Expressions in f-strings
print(f"Double: {count * 2}")                 # Double: 20
print(f"Total: {count + 5}")                  # Total: 15

# .format() method
print("Bird: {}".format(species))             # Bird: Sparrow
print("Count: {}, Weight: {}g".format(count, weight))

# Positional and keyword arguments
print("{1} {0}".format("Sparrow", "Eagle"))   # Eagle Sparrow
print("{species}: {count}".format(species="Eagle", count=3))

# % formatting (older)
print("Bird: %s" % species)                   # Bird: Sparrow
print("Count: %d, Weight: %.2f" % (count, weight))

# Alignment and padding
print(f"{species:>10}")                       # Right align
print(f"{species:<10}")                       # Left align
print(f"{species:^10}")                       # Center

9.4 Regular Expressions (re module)

Regular expressions provide pattern matching for text.

The re module provides regex functionality. Patterns use special characters for matching. Used for validation, searching, and replacement.

Code Example

import re

text = "Sparrow 10, Eagle 3, Hawk 5"

# Search
match = re.search(r'Eagle', text)
print(match.group())    # Eagle

# Find all
birds = re.findall(r'\w+', text)  # ['Sparrow', '10', 'Eagle', '3', 'Hawk', '5']
numbers = re.findall(r'\d+', text)  # ['10', '3', '5']

# Find pattern with groups
pattern = r'(\w+)\s+(\d+)'
matches = re.findall(pattern, text)
for bird, count in matches:
    print(f"{bird}: {count}")

# Substitution
replaced = re.sub(r'\d+', 'X', text)  # "Sparrow X, Eagle X, Hawk X"

# Compile pattern for efficiency
pattern = re.compile(r'\b[A-Z][a-z]+\b')
birds = pattern.findall(text)  # ['Sparrow', 'Eagle', 'Hawk']

# Check if string matches
email_pattern = r'^[\w\.-]+@[\w\.-]+\.\w+$'
print(bool(re.match(email_pattern, 'bird@example.com')))  # True

Modules & Packages

10.1 Creating Modules

Modules are Python files containing reusable code.

Any .py file is a module. Modules organize code, avoid duplication, and provide namespaces. They can contain functions, classes, and variables.

Code Example

# bird_module.py (module file)
"""Module containing bird-related functions and classes"""

# Constants
MAX_BIRDS = 100

# Variables
bird_types = ["Sparrow", "Eagle", "Hawk"]

# Functions
def count_birds(birds_dict):
    """Count total birds in dictionary"""
    return sum(birds_dict.values())

# Classes
class Bird:
    def __init__(self, species, count):
        self.species = species
        self.count = count
    
    def display(self):
        print(f"{self.species}: {self.count}")

10.2 Importing Modules

Importing allows using code from other modules.

import brings module into current namespace. from imports specific items. as creates aliases.

Code Example

# Import entire module
import bird_module
print(bird_module.MAX_BIRDS)
bird_module.count_birds({"Sparrow": 10, "Eagle": 3})

# Import specific items
from bird_module import count_birds, Bird
print(count_birds({"Sparrow": 10, "Eagle": 3}))
b = Bird("Hawk", 5)

# Import with alias
import bird_module as bm
print(bm.MAX_BIRDS)

# Import all items (not recommended)
from bird_module import *
print(MAX_BIRDS)

# Built-in module import
import math
import random
import datetime

10.3 Built-in Modules (math, random, datetime, os, sys)

Built-in modules provide common functionality without installation.

math: Mathematical operations. random: Random number generation. datetime: Date and time operations. os: Operating system interactions. sys: System-specific parameters.

Code Example

# math module
import math

print(math.pi)                  # 3.14159
print(math.sqrt(16))            # 4.0
print(math.factorial(5))        # 120
print(math.pow(2, 3))           # 8.0

# random module
import random

print(random.random())          # Random float 0-1
print(random.randint(1, 10))    # Random integer 1-10
birds = ["Sparrow", "Eagle", "Hawk"]
print(random.choice(birds))     # Random choice
random.shuffle(birds)           # Shuffle list

# datetime module
import datetime

now = datetime.datetime.now()
print(now)
print(now.year, now.month, now.day)
delta = datetime.timedelta(days=7)
print(now + delta)

# os module
import os

print(os.getcwd())              # Current directory
# os.mkdir("new_folder")        # Create directory
print(os.listdir("."))          # List files

# sys module
import sys

print(sys.version)              # Python version
print(sys.platform)             # Operating system

10.4 Creating Packages

Packages organize related modules in directories.

A package is a directory with __init__.py file. Packages can have sub-packages. They provide hierarchical organization.

Code Example

# Directory structure
bird_package/
    __init__.py
    birds.py
    data/
        __init__.py
        bird_data.py
    utils/
        __init__.py
        helpers.py

# __init__.py (makes directory a package)
# Can contain initialization code

# birds.py (module in package)
def count_birds(birds_dict):
    return sum(birds_dict.values())

# Importing from package
from bird_package import birds
from bird_package.data import bird_data
from bird_package.utils import helpers

# Using package
birds.count_birds({"Sparrow": 10, "Eagle": 3})

Object-Oriented Programming (OOP)

11.1 Classes & Objects

Classes are blueprints for objects. Objects are instances of classes.

Classes define attributes (data) and methods (functions). Objects are concrete instances with specific values. Classes enable encapsulation and code organization.

Code Example

# Class definition
class Bird:
    # Class attribute (shared by all instances)
    species_count = 0
    
    # Constructor (initialize object)
    def __init__(self, species, count):
        self.species = species      # Instance attribute
        self.count = count          # Instance attribute
        Bird.species_count += 1     # Update class attribute
    
    # Method (function in class)
    def display(self):
        print(f"{self.species}: {self.count} birds")
    
    def add_birds(self, number):
        self.count += number
        print(f"Added {number}. New count: {self.count}")

# Creating objects (instances)
sparrow = Bird("Sparrow", 10)
eagle = Bird("Eagle", 3)

# Using objects
sparrow.display()                  # Sparrow: 10 birds
eagle.display()                    # Eagle: 3 birds
print(Bird.species_count)          # 2

# Modify object
sparrow.add_birds(5)               # Added 5. New count: 15
sparrow.display()                  # Sparrow: 15 birds

# Accessing attributes
print(sparrow.species)             # Sparrow
print(sparrow.count)               # 15

11.2 Constructors (init)

__init__ is the constructor method called when creating objects.

The constructor initializes object attributes. It’s called automatically. Can have parameters for custom initialization.

Code Example

class Bird:
    def __init__(self, species, count, weight=0.0):
        self.species = species
        self.count = count
        self.weight = weight
        self.is_active = True
        print(f"Bird {species} created")
    
    def display(self):
        print(f"{self.species}: {self.count} birds, {self.weight}g")

# Creating objects with different parameters
sparrow = Bird("Sparrow", 10, 25.5)
eagle = Bird("Eagle", 3)      # Uses default weight
hawk = Bird("Hawk", 5)

sparrow.display()
eagle.display()

11.3 Encapsulation (private variables)

Encapsulation hides internal data and exposes controlled access.

Private variables use double underscore __ prefix. They’re name-mangled to prevent accidental access. Getters and setters control access.

Code Example

class Bird:
    def __init__(self, species, count):
        self.species = species
        self.__count = count      # Private variable
        self.__weight = 0.0       # Private variable
    
    # Getter
    def get_count(self):
        return self.__count
    
    # Setter with validation
    def set_count(self, count):
        if count >= 0:
            self.__count = count
        else:
            print("Error: Count cannot be negative")
    
    # Getter
    def get_weight(self):
        return self.__weight
    
    # Setter with validation
    def set_weight(self, weight):
        if weight > 0:
            self.__weight = weight
        else:
            print("Error: Weight must be positive")
    
    def display(self):
        print(f"{self.species}: {self.__count} birds, {self.__weight}g")

# Usage
sparrow = Bird("Sparrow", 10)
print(sparrow.species)          # Sparrow (public)
# print(sparrow.__count)        # Error: private

print(sparrow.get_count())      # 10 (access via getter)
sparrow.set_count(15)           # Modify via setter
sparrow.set_weight(25.5)        # Set weight
sparrow.display()

11.4 Inheritance

Inheritance creates a hierarchy of classes that share attributes and methods.

Child classes inherit from parent classes. They can override methods and add new ones. super() calls parent methods.

Code Example

# Parent class
class Bird:
    def __init__(self, species, count):
        self.species = species
        self.count = count
    
    def fly(self):
        print(f"{self.species} is flying")
    
    def display(self):
        print(f"{self.species}: {self.count} birds")

# Child class (inherits from Bird)
class Eagle(Bird):
    def __init__(self, species, count, wingspan):
        super().__init__(species, count)    # Call parent constructor
        self.wingspan = wingspan            # New attribute
    
    def hunt(self):                         # New method
        print(f"{self.species} is hunting")
    
    def fly(self):                          # Override method
        print(f"{self.species} soars with {self.wingspan}m wingspan")
    
    def display(self):                      # Override method
        super().display()                   # Call parent method
        print(f"Wingspan: {self.wingspan}m")

# Another child class
class Sparrow(Bird):
    def chirp(self):
        print(f"{self.species} chirps: Chirp chirp!")

# Usage
eagle = Eagle("Golden Eagle", 3, 2.3)
sparrow = Sparrow("House Sparrow", 10)

# Inherited methods
eagle.display()          # Calls overridden method
eagle.fly()             # Calls overridden method
sparrow.fly()           # Inherited method

# New methods
eagle.hunt()            # Eagle-specific
sparrow.chirp()         # Sparrow-specific

11.5 Polymorphism

Polymorphism allows different objects to respond to the same method call in their own way, enabling the same interface to work with different implementations.

Different classes can have methods with the same name. The correct method is determined at runtime. Enables flexible code design.

Code Example

class Bird:
    def speak(self):
        return "Generic bird sound"

class Eagle(Bird):
    def speak(self):
        return "Screech!"

class Sparrow(Bird):
    def speak(self):
        return "Chirp chirp!"

class Parrot(Bird):
    def speak(self):
        return "Hello! I can talk!"

def bird_sound(bird):
    """Function that works with any bird"""
    print(bird.speak())

# Creating different birds
birds = [
    Eagle(),
    Sparrow(),
    Parrot(),
    Bird()
]

# Polymorphism in action
for bird in birds:
    bird_sound(bird)
    # Output:
    # Screech!
    # Chirp chirp!
    # Hello! I can talk!
    # Generic bird sound

# Function expecting any bird type
def report_bird(bird):
    print(f"Bird says: {bird.speak()}")

11.6 Abstraction (abstract classes)

Abstract classes define interfaces that must be implemented by derived classes.

Abstract classes cannot be instantiated. They contain abstract methods that derived classes must implement. They define a contract for subclasses.

Code Example

from abc import ABC, abstractmethod

# Abstract class
class Bird(ABC):
    @abstractmethod
    def fly(self):
        """All birds must fly"""
        pass
    
    @abstractmethod
    def speak(self):
        """All birds must speak"""
        pass
    
    def display(self):
        print("This is a bird")

# Concrete class implementing Bird
class Eagle(Bird):
    def fly(self):
        print("Eagle soars high")
    
    def speak(self):
        print("Screech!")

class Sparrow(Bird):
    def fly(self):
        print("Sparrow flutters")
    
    def speak(self):
        print("Chirp chirp!")

# Cannot instantiate abstract class
# bird = Bird()  # TypeError

# Can instantiate concrete classes
eagle = Eagle()
sparrow = Sparrow()

# All birds have same interface
def bird_actions(bird):
    bird.fly()
    bird.speak()

bird_actions(eagle)   # Eagle soars high, Screech!
bird_actions(sparrow) # Sparrow flutters, Chirp chirp!

File Handling

12.1 Reading Files

Reading files extracts data from text files.

Files are opened, read, and closed. Various reading methods are available. Context managers (with) handle file closing automatically.

Code Example

# Method 1: read entire file
with open('birds.txt', 'r') as file:
    content = file.read()
    print(content)

# Method 2: read lines
with open('birds.txt', 'r') as file:
    lines = file.readlines()
    for line in lines:
        print(line.strip())

# Method 3: iterate over file
with open('birds.txt', 'r') as file:
    for line in file:
        print(line.strip())

# Method 4: read specific number of characters
with open('birds.txt', 'r') as file:
    data = file.read(10)
    print(data)

12.2 Writing Files

Writing files saves data to text files.

write() writes a string. writelines() writes multiple lines. a mode appends to existing file.

Code Example

# Write to file (overwrites existing)
with open('birds.txt', 'w') as file:
    file.write("Sparrow, Eagle, Hawk\n")
    file.write("Finch, Robin, Cardinal\n")

# Write multiple lines
lines = ["Sparrow, Eagle, Hawk\n", "Finch, Robin, Cardinal\n"]
with open('birds.txt', 'w') as file:
    file.writelines(lines)

# Append to file
with open('birds.txt', 'a') as file:
    file.write("Dove, Crow\n")

# Write with format
bird_data = {"Sparrow": 10, "Eagle": 3, "Hawk": 5}
with open('bird_counts.txt', 'w') as file:
    for species, count in bird_data.items():
        file.write(f"{species}: {count}\n")

12.3 File Modes

File modes specify how to open files.

Common modes: 'r' (read), 'w' (write), 'a' (append), 'x' (exclusive creation), 'r+' (read and write), 'b' (binary mode).

Code Example

# Read mode
with open('file.txt', 'r') as file:
    content = file.read()

# Write mode (overwrite)
with open('file.txt', 'w') as file:
    file.write("New content")

# Append mode (add to end)
with open('file.txt', 'a') as file:
    file.write("Additional content")

# Read and write mode
with open('file.txt', 'r+') as file:
    content = file.read()
    file.write("Appended")

# Binary mode
with open('image.jpg', 'rb') as file:
    binary_data = file.read()

# Exclusive creation (fails if file exists)
try:
    with open('new_file.txt', 'x') as file:
        file.write("New file created")
except FileExistsError:
    print("File already exists")

12.4 Working with CSV Files

CSV files store tabular data with comma-separated values.

The csv module handles CSV operations. reader reads CSV files. writer writes CSV files. DictReader and DictWriter use dictionaries.

Code Example

import csv

# Writing CSV
bird_data = [
    ['Species', 'Count', 'Weight'],
    ['Sparrow', '10', '25.5'],
    ['Eagle', '3', '4500.0'],
    ['Hawk', '5', '1200.0']
]

with open('birds.csv', 'w', newline='') as file:
    writer = csv.writer(file)
    writer.writerows(bird_data)

# Reading CSV
with open('birds.csv', 'r') as file:
    reader = csv.reader(file)
    for row in reader:
        print(row)

# Using DictWriter
fieldnames = ['Species', 'Count', 'Weight']
with open('birds_dict.csv', 'w', newline='') as file:
    writer = csv.DictWriter(file, fieldnames=fieldnames)
    writer.writeheader()
    writer.writerow({'Species': 'Sparrow', 'Count': '10', 'Weight': '25.5'})

# Using DictReader
with open('birds_dict.csv', 'r') as file:
    reader = csv.DictReader(file)
    for row in reader:
        print(row['Species'], row['Count'])

12.5 Working with JSON Files

JSON files store structured data in JavaScript Object Notation format.

JSON is human-readable and language-independent. json module handles serialization and deserialization. dump writes JSON, load reads JSON.

Code Example

import json

# JSON data
bird_data = {
    "Sparrow": {"count": 10, "weight": 25.5},
    "Eagle": {"count": 3, "weight": 4500.0},
    "Hawk": {"count": 5, "weight": 1200.0}
}

# Write JSON to file
with open('birds.json', 'w') as file:
    json.dump(bird_data, file, indent=4)

# Read JSON from file
with open('birds.json', 'r') as file:
    loaded_data = json.load(file)
print(loaded_data)

# JSON to Python object (loads)
json_string = '{"Sparrow": 10, "Eagle": 3}'
data = json.loads(json_string)
print(data)

# Python object to JSON (dumps)
python_data = {"Sparrow": 10, "Eagle": 3}
json_string = json.dumps(python_data, indent=2)
print(json_string)

# Handling dates in JSON
from datetime import datetime
data = {"date": datetime.now().isoformat()}
with open('date.json', 'w') as file:
    json.dump(data, file)

Error Handling (Exceptions)

13.1 try, except

try-except blocks handle runtime errors gracefully.

Code that might cause errors goes in try block. Error handling goes in except blocks. Multiple exception types can be caught.

Code Example

# Basic try-except
try:
    birds = [10, 20, 30]
    print(birds[5])  # IndexError
except:
    print("Error occurred")

# Catching specific exceptions
try:
    count = int("abc")  # ValueError
except ValueError:
    print("Invalid number")

# Multiple except blocks
try:
    birds = [10, 20, 30]
    print(birds[5])
except IndexError:
    print("Index out of range")
except ValueError:
    print("Invalid value")

# Exception with variable
try:
    bird_count = int(input("Enter count: "))
except ValueError as e:
    print(f"Error: {e}")

# Multiple exceptions in one block
try:
    bird_count = int("abc")
except (ValueError, TypeError) as e:
    print(f"Error: {e}")

# try with else (runs if no error)
try:
    bird_count = 10
except:
    print("Error occurred")
else:
    print("No error, count is", bird_count)

13.2 finally

finally block executes regardless of errors.

finally runs whether an exception occurred or not. Used for cleanup operations like closing files or connections.

Code Example

# Basic finally
try:
    bird_count = 10
    print("Processing")
except:
    print("Error")
finally:
    print("Always executes")

# File handling with finally
file = None
try:
    file = open('birds.txt', 'r')
    content = file.read()
except FileNotFoundError:
    print("File not found")
finally:
    if file:
        file.close()  # Ensure file closes
        print("File closed")

# Better approach - with context manager
try:
    with open('birds.txt', 'r') as file:
        content = file.read()
except FileNotFoundError:
    print("File not found")

# Cleaning up resources
def process_birds():
    try:
        print("Processing birds")
        return 10  # Return value
    finally:
        print("Cleanup executed")

result = process_birds()  # "Cleanup executed" before return
print(result)            # 10

13.3 raise

raise manually triggers exceptions.

raise is used to signal errors. Can create and raise custom exceptions. Exceptions propagate to calling code.

Code Example

# Raising built-in exception
def check_bird_count(count):
    if count < 0:
        raise ValueError("Count cannot be negative")
    return count

try:
    check_bird_count(-5)
except ValueError as e:
    print(f"Error: {e}")

# Raising with custom message
def get_bird(species):
    if species == "":
        raise Exception("Species cannot be empty")
    return species

# Re-raising exception
try:
    bird_count = int("abc")
except ValueError:
    print("Invalid input")
    raise  # Re-raise the exception

# Custom validation
def validate_bird_data(species, count):
    if not species:
        raise ValueError("Species required")
    if count <= 0:
        raise ValueError("Count must be positive")
    return True

try:
    validate_bird_data("", 10)
except ValueError as e:
    print(f"Validation error: {e}")

13.4 Custom Exceptions

Custom exceptions are user-defined exception classes.

Custom exceptions inherit from Exception. They can have additional attributes and methods. They provide specific error types.

Code Example

# Custom exception class
class BirdError(Exception):
    """Base class for bird-related errors"""
    pass

class BirdNotFoundError(BirdError):
    """Raised when a bird is not found"""
    def __init__(self, species):
        self.species = species
        self.message = f"Bird {species} not found"
        super().__init__(self.message)

class BirdCountError(BirdError):
    """Raised when bird count is invalid"""
    def __init__(self, count, message="Invalid count"):
        self.count = count
        self.message = f"{message}: {count}"
        super().__init__(self.message)

# Using custom exceptions
def get_bird_count(species, bird_data):
    if species not in bird_data:
        raise BirdNotFoundError(species)
    return bird_data[species]

def set_bird_count(species, count):
    if count < 0:
        raise BirdCountError(count, "Count cannot be negative")
    print(f"{species}: {count}")

# Handling custom exceptions
bird_data = {"Sparrow": 10, "Eagle": 3}

try:
    print(get_bird_count("Hawk", bird_data))
except BirdNotFoundError as e:
    print(f"Error: {e}")

try:
    set_bird_count("Sparrow", -5)
except BirdCountError as e:
    print(f"Error: {e}")

# Catching specific exceptions
try:
    get_bird_count("Finch", bird_data)
except BirdNotFoundError as e:
    print(f"Bird not found: {e.species}")
except BirdError as e:
    print(f"General bird error: {e}")

Advanced Python

14.1 Decorators

Decorators modify or enhance functions without changing their code.

Decorators are higher-order functions. They wrap functions with additional behavior. Used for logging, timing, authentication, etc.

Code Example

# Basic decorator
def timer(func):
    import time
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} took {end - start:.4f} seconds")
        return result
    return wrapper

@timer
def count_birds():
    total = 0
    for i in range(1000000):
        total += i
    return total

count_birds()  # Prints timing info

# Decorator with parameters
def repeat(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(times):
                func(*args, **kwargs)
        return wrapper
    return decorator

@repeat(3)
def bird():
    print("Bird!")

bird()  # Prints "Bird!" 3 times

# Practical decorator - logging
def log_function(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with {args}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} returned {result}")
        return result
    return wrapper

@log_function
def add_birds(a, b):
    return a + b

add_birds(10, 5)

# Multiple decorators
@timer
@log_function
def process_birds():
    return 42

14.2 Generators (yield)

Generators produce values lazily using yield.

Generators create iterators. They generate values on demand, saving memory. yield pauses and resumes function execution.

Code Example

# Basic generator
def count_birds():
    yield "Sparrow"
    yield "Eagle"
    yield "Hawk"

birds = count_birds()
print(next(birds))  # Sparrow
print(next(birds))  # Eagle
print(next(birds))  # Hawk

# Generator with loop
def bird_generator(n):
    for i in range(n):
        yield f"Bird {i}"

for bird in bird_generator(5):
    print(bird)

# Infinite generator
def infinite_birds():
    count = 0
    while True:
        count += 1
        yield f"Bird {count}"

bird_gen = infinite_birds()
print(next(bird_gen))  # Bird 1
print(next(bird_gen))  # Bird 2
print(next(bird_gen))  # Bird 3

# Generator expression
numbers = (x**2 for x in range(10))
for num in numbers:
    print(num)

# Practical - reading large file
def read_large_file(file_path):
    with open(file_path, 'r') as file:
        for line in file:
            yield line.strip()

# for line in read_large_file('big_file.txt'):
#     process(line)

# Generator with multiple yields
def multiple_yields():
    print("First yield")
    yield 1
    print("Second yield")
    yield 2
    print("Third yield")
    yield 3

for value in multiple_yields():
    print(value)

14.3 Iterators

Iterators provide sequential access to collections.

Iterators implement __iter__() and __next__(). They’re used with for loops. Custom iterators can be created.

Code Example

# Basic iterator example
birds = ["Sparrow", "Eagle", "Hawk"]
iterator = iter(birds)

print(next(iterator))  # Sparrow
print(next(iterator))  # Eagle
print(next(iterator))  # Hawk

# Custom iterator
class BirdIterator:
    def __init__(self, birds):
        self.birds = birds
        self.index = 0
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.index >= len(self.birds):
            raise StopIteration
        bird = self.birds[self.index]
        self.index += 1
        return bird

birds = ["Sparrow", "Eagle", "Hawk"]
bird_iter = BirdIterator(birds)

for bird in bird_iter:
    print(bird)

# Using iter with built-in functions
text = "Eagle"
letter_iter = iter(text)
print(list(letter_iter))  # ['E', 'a', 'g', 'l', 'e']

# Creating iterator from range
range_iter = iter(range(3))
print(next(range_iter))  # 0
print(next(range_iter))  # 1

14.4 Context Managers

Context managers manage resources using with statements.

Context managers set up and tear down resources automatically. They use __enter__ and __exit__ methods. The with statement ensures proper cleanup.

Code Example

# File context manager (built-in)
with open('file.txt', 'w') as file:
    file.write("Hello")

# Custom context manager using class
class BirdContext:
    def __enter__(self):
        print("Setting up bird context")
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("Cleaning up bird context")
    
    def count(self):
        print("Counting birds")

with BirdContext() as bc:
    bc.count()

# Custom context manager using contextlib
from contextlib import contextmanager

@contextmanager
def bird_manager():
    print("Entering")
    try:
        yield "Bird data"
    finally:
        print("Exiting")

with bird_manager() as data:
    print(f"Processing: {data}")

# Practical - database connection
@contextmanager
def database_connection():
    print("Connecting to database")
    conn = "Database connection"
    try:
        yield conn
    finally:
        print("Closing database connection")

with database_connection() as db:
    print(f"Using {db}")

14.5 Multithreading

Multithreading runs multiple threads concurrently.

Threads allow parallel execution within a single process. threading module provides thread management. Useful for I/O-bound tasks.

Code Example

import threading
import time

# Basic thread
def count_birds(count):
    for i in range(count):
        print(f"Bird {i} from thread {threading.current_thread().name}")
        time.sleep(0.1)

# Create and start threads
thread1 = threading.Thread(target=count_birds, args=(3,))
thread2 = threading.Thread(target=count_birds, args=(3,))

thread1.start()
thread2.start()

thread1.join()
thread2.join()

print("All threads complete")

# Thread with shared resource
counter = 0
lock = threading.Lock()

def increment():
    global counter
    for _ in range(1000):
        with lock:  # Thread-safe operation
            counter += 1

threads = []
for _ in range(10):
    t = threading.Thread(target=increment)
    threads.append(t)
    t.start()

for t in threads:
    t.join()

print(f"Counter: {counter}")  # Should be 10000

# Thread with arguments
def bird_thread(species, count):
    print(f"{species}: {count}")

t = threading.Thread(target=bird_thread, args=("Sparrow", 10))
t.start()
t.join()

14.6 Multiprocessing

Multiprocessing runs multiple processes for CPU-intensive tasks.

Processes have separate memory space. multiprocessing module provides process management. Bypasses GIL for true parallelism.

Code Example

import multiprocessing
import time

# CPU-intensive task
def count_birds_heavy(count):
    total = 0
    for i in range(count):
        total += i
    return total

# Using Process
def process_function(start, end):
    total = 0
    for i in range(start, end):
        total += i
    return total

with multiprocessing.Pool() as pool:
    results = pool.map(count_birds_heavy, [1000000, 1000000])
    print(results)

# Process pool
def compute_bird_data(n):
    return sum(range(n))

if __name__ == "__main__":
    with multiprocessing.Pool(processes=4) as pool:
        data = [1000000, 2000000, 3000000, 4000000]
        results = pool.map(compute_bird_data, data)
        print(results)

# Shared memory
import multiprocessing as mp

def worker(shared_list):
    shared_list.append("Bird")

if __name__ == "__main__":
    manager = mp.Manager()
    shared_list = manager.list()
    
    processes = []
    for _ in range(5):
        p = mp.Process(target=worker, args=(shared_list,))
        processes.append(p)
        p.start()
    
    for p in processes:
        p.join()
    
    print(shared_list)

14.7 Async Programming (asyncio)

Async programming handles concurrent operations without threads.

asyncio enables asynchronous programming. async/await syntax for coroutines. Ideal for I/O-bound, network, and database operations.

Code Example

import asyncio
import time

# Basic async function
async def bird_async():
    print("Bird starts")
    await asyncio.sleep(1)
    print("Bird finishes")
    return "Result"

# Run async function
result = asyncio.run(bird_async())
print(result)

# Multiple async tasks
async def count_birds(species, delay):
    await asyncio.sleep(delay)
    print(f"Counted {species} after {delay}s")
    return f"{species}: done"

async def main():
    tasks = [
        count_birds("Sparrow", 2),
        count_birds("Eagle", 1),
        count_birds("Hawk", 3)
    ]
    results = await asyncio.gather(*tasks)
    print(results)

asyncio.run(main())

# Async with asyncio.create_task
async def process():
    task1 = asyncio.create_task(count_birds("Sparrow", 2))
    task2 = asyncio.create_task(count_birds("Eagle", 1))
    await task1
    await task2

# Async context manager
async def async_context():
    async with asyncio.timeout(2):
        await asyncio.sleep(1)
        print("Success")
    print("Done")

asyncio.run(async_context())

Data Structures & Algorithms in Python

15.1 Complexity Analysis (Time, Space, Big-O)

Complexity analysis measures algorithm efficiency.

Time complexity describes how an algorithm’s running time changes as the input size grows. Space complexity describes how its memory usage changes with input size. Big-O notation expresses the growth rate of an algorithm’s time or space requirements.

Code Example

# O(1) - Constant time
def get_first_bird(birds):
    return birds[0]  # Always same time

# O(n) - Linear time
def find_bird(birds, target):
    for bird in birds:
        if bird == target:
            return True
    return False

# O(log n) - Logarithmic
def binary_search(birds, target):
    left, right = 0, len(birds) - 1
    while left <= right:
        mid = (left + right) // 2
        if birds[mid] == target:
            return True
        elif birds[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return False

# O(n²) - Quadratic
def find_duplicates(birds):
    duplicates = []
    for i in range(len(birds)):
        for j in range(i + 1, len(birds)):
            if birds[i] == birds[j]:
                duplicates.append(birds[i])
    return duplicates

# O(n log n) - Linearithmic
def sort_birds(birds):
    return sorted(birds)  # Timsort is O(n log n)

# Analyzing complexity
def sum_list(numbers):
    total = 0           # O(1)
    for n in numbers:   # O(n)
        total += n
    return total        # O(1)
# Total: O(n)

15.2 Stack (LIFO)

Stack is a LIFO (Last-In-First-Out) data structure.

Stack operations: push (add to top), pop (remove from top), peek (view top). Implemented with list in Python.

Code Example

# Stack implementation using list
class BirdStack:
    def __init__(self):
        self.items = []
    
    def push(self, bird):
        self.items.append(bird)
        print(f"Pushed: {bird}")
    
    def pop(self):
        if not self.is_empty():
            return self.items.pop()
        print("Stack is empty!")
        return None
    
    def peek(self):
        if not self.is_empty():
            return self.items[-1]
        return None
    
    def is_empty(self):
        return len(self.items) == 0
    
    def size(self):
        return len(self.items)
    
    def display(self):
        print("Stack:", self.items)

# Using stack
stack = BirdStack()
stack.push("Sparrow")
stack.push("Eagle")
stack.push("Hawk")

print("Top:", stack.peek())  # Hawk
print("Pop:", stack.pop())   # Hawk
print("Top:", stack.peek())  # Eagle

# Stack with list operations directly
birds = []
birds.append("Sparrow")    # Push
birds.append("Eagle")
birds.append("Hawk")
print(birds.pop())          # Hawk

15.3 Queue (FIFO)

Queue is a FIFO (First-In-First-Out) data structure.

Queue operations: enqueue (add to back), dequeue (remove from front). Implemented with collections.deque for efficiency.

Code Example

from collections import deque

# Queue implementation using deque
class BirdQueue:
    def __init__(self):
        self.items = deque()
    
    def enqueue(self, bird):
        self.items.append(bird)
        print(f"Enqueued: {bird}")
    
    def dequeue(self):
        if not self.is_empty():
            return self.items.popleft()
        print("Queue is empty!")
        return None
    
    def peek(self):
        if not self.is_empty():
            return self.items[0]
        return None
    
    def is_empty(self):
        return len(self.items) == 0
    
    def size(self):
        return len(self.items)
    
    def display(self):
        print("Queue:", list(self.items))

# Using queue
queue = BirdQueue()
queue.enqueue("Sparrow")
queue.enqueue("Eagle")
queue.enqueue("Hawk")

print("Front:", queue.peek())  # Sparrow
print("Dequeue:", queue.dequeue())  # Sparrow
print("Front:", queue.peek())  # Eagle

# Queue using list (less efficient)
class SimpleQueue:
    def __init__(self):
        self.items = []
    
    def enqueue(self, item):
        self.items.append(item)
    
    def dequeue(self):
        if self.items:
            return self.items.pop(0)  # O(n) - inefficient

15.4 Linked List

Linked list is a sequence of nodes where each node references the next.

Nodes contain data and pointer to next node. Singly linked list (forward only). Doubly linked list (forward and backward).

Code Example

# Node class
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

# Singly Linked List
class BirdList:
    def __init__(self):
        self.head = None
    
    def append(self, data):
        new_node = Node(data)
        if not self.head:
            self.head = new_node
            return
        current = self.head
        while current.next:
            current = current.next
        current.next = new_node
    
    def prepend(self, data):
        new_node = Node(data)
        new_node.next = self.head
        self.head = new_node
    
    def delete(self, data):
        if not self.head:
            return
        if self.head.data == data:
            self.head = self.head.next
            return
        current = self.head
        while current.next:
            if current.next.data == data:
                current.next = current.next.next
                return
            current = current.next
    
    def display(self):
        current = self.head
        while current:
            print(current.data, end=" -> ")
            current = current.next
        print("None")

# Using linked list
birds = BirdList()
birds.append("Sparrow")
birds.append("Eagle")
birds.prepend("Hawk")
birds.display()  # Hawk -> Sparrow -> Eagle -> None

birds.delete("Sparrow")
birds.display()  # Hawk -> Eagle -> None

15.5 Trees

Trees are hierarchical data structures with parent-child relationships.

Binary trees have up to two children. Binary search trees have ordered nodes. Tree traversal includes inorder, preorder, postorder.

Code Example

# Binary Tree Node
class TreeNode:
    def __init__(self, data):
        self.data = data
        self.left = None
        self.right = None

# Binary Search Tree
class BirdTree:
    def __init__(self):
        self.root = None
    
    def insert(self, data):
        if not self.root:
            self.root = TreeNode(data)
        else:
            self._insert(self.root, data)
    
    def _insert(self, node, data):
        if data < node.data:
            if node.left:
                self._insert(node.left, data)
            else:
                node.left = TreeNode(data)
        else:
            if node.right:
                self._insert(node.right, data)
            else:
                node.right = TreeNode(data)
    
    def inorder(self):
        result = []
        self._inorder(self.root, result)
        return result
    
    def _inorder(self, node, result):
        if node:
            self._inorder(node.left, result)
            result.append(node.data)
            self._inorder(node.right, result)
    
    def search(self, data):
        return self._search(self.root, data)
    
    def _search(self, node, data):
        if not node or node.data == data:
            return node
        if data < node.data:
            return self._search(node.left, data)
        return self._search(node.right, data)

# Using tree
tree = BirdTree()
birds = ["Sparrow", "Eagle", "Hawk", "Cardinal", "Robin"]
for bird in birds:
    tree.insert(bird)

print("Inorder:", tree.inorder())  # Alphabetical order
print("Search Eagle:", tree.search("Eagle") is not None)

15.6 Graphs

Graphs represent connections between nodes.

Graphs have nodes (vertices) and edges. Directed vs undirected. Weighted vs unweighted. Represented with adjacency list or matrix.

Code Example

# Graph using adjacency list
class BirdGraph:
    def __init__(self):
        self.graph = {}
    
    def add_node(self, node):
        if node not in self.graph:
            self.graph[node] = []
    
    def add_edge(self, node1, node2):
        if node1 not in self.graph:
            self.add_node(node1)
        if node2 not in self.graph:
            self.add_node(node2)
        self.graph[node1].append(node2)
        self.graph[node2].append(node1)  # For undirected graph
    
    def display(self):
        for node, neighbors in self.graph.items():
            print(f"{node} -> {neighbors}")
    
    def bfs(self, start):
        visited = set()
        queue = [start]
        visited.add(start)
        result = []
        
        while queue:
            node = queue.pop(0)
            result.append(node)
            for neighbor in self.graph.get(node, []):
                if neighbor not in visited:
                    visited.add(neighbor)
                    queue.append(neighbor)
        return result
    
    def dfs(self, start):
        visited = set()
        result = []
        self._dfs(start, visited, result)
        return result
    
    def _dfs(self, node, visited, result):
        visited.add(node)
        result.append(node)
        for neighbor in self.graph.get(node, []):
            if neighbor not in visited:
                self._dfs(neighbor, visited, result)

# Using graph
graph = BirdGraph()
birds = ["Sparrow", "Eagle", "Hawk", "Robin"]
for bird in birds:
    graph.add_node(bird)

graph.add_edge("Sparrow", "Eagle")
graph.add_edge("Sparrow", "Hawk")
graph.add_edge("Eagle", "Robin")
graph.add_edge("Hawk", "Robin")

graph.display()
print("BFS:", graph.bfs("Sparrow"))
print("DFS:", graph.dfs("Sparrow"))

15.7 Sorting Algorithms (Bubble, Merge, Quick)

Sorting algorithms arrange elements in specific order.

Bubble Sort: O(n²), simple but slow. Merge Sort is a stable sorting algorithm with O(n log n) time complexity. It divides the array into smaller parts, sorts them, and then merges the sorted parts. It typically requires O(n) extra memory for the merging process. Quick Sort: O(n log n) average, in-place sorting.

Code Example

# Bubble Sort (O(n²))
def bubble_sort(arr):
    n = len(arr)
    for i in range(n):
        for j in range(0, n - i - 1):
            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
    return arr

# Merge Sort (O(n log n))
def merge_sort(arr):
    if len(arr) <= 1:
        return arr
    mid = len(arr) // 2
    left = merge_sort(arr[:mid])
    right = merge_sort(arr[mid:])
    return merge(left, right)

def merge(left, right):
    result = []
    i, j = 0, 0
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    result.extend(left[i:])
    result.extend(right[j:])
    return result

# Quick Sort (O(n log n) average)
def quick_sort(arr):
    if len(arr) <= 1:
        return arr
    pivot = arr[-1]
    left = [x for x in arr[:-1] if x <= pivot]
    right = [x for x in arr[:-1] if x > pivot]
    return quick_sort(left) + [pivot] + quick_sort(right)

# Testing
birds = [34, 7, 23, 32, 5, 62]
print("Original:", birds)
print("Bubble:", bubble_sort(birds.copy()))
print("Merge:", merge_sort(birds.copy()))
print("Quick:", quick_sort(birds.copy()))

# String sorting
bird_names = ["Eagle", "Sparrow", "Hawk", "Robin"]
print("Sorted names:", sorted(bird_names))  # Built-in sort

15.8 Searching Algorithms (Linear, Binary)

Searching algorithms find specific elements in data structures.

Linear Search: O(n), works on unsorted data. Binary Search: O(log n), requires sorted data.

Code Example

# Linear Search (O(n))
def linear_search(arr, target):
    for i, item in enumerate(arr):
        if item == target:
            return i
    return -1

# Binary Search (O(log n))
def binary_search(arr, target):
    left, right = 0, len(arr) - 1
    while left <= right:
        mid = (left + right) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return -1

# Testing
birds = [5, 12, 23, 32, 45, 62, 78, 91]
target = 45

# Linear Search
pos = linear_search(birds, target)
print(f"Linear: Found {target} at position {pos}")

# Binary Search (requires sorted array)
birds_sorted = sorted(birds)
pos = binary_search(birds_sorted, target)
print(f"Binary: Found {target} at position {pos}")

# Search in list of strings
names = ["Eagle", "Hawk", "Robin", "Sparrow"]
print("Find Eagle:", names.index("Eagle"))  # Built-in

# Performance comparison
import time
data = list(range(1000000))

start = time.time()
linear_search(data, 999999)
print(f"Linear: {time.time() - start:.4f}s")

start = time.time()
binary_search(data, 999999)
print(f"Binary: {time.time() - start:.4f}s")

Python Ecosystem

16.1 Database Programming (SQLite, MySQL, PostgreSQL)

Database programming enables persistent data storage.

SQLite: Lightweight, file-based database. MySQL/PostgreSQL: Server-based databases. Python provides modules for each.

Code Example

import sqlite3

# SQLite connection
conn = sqlite3.connect('birds.db')
cursor = conn.cursor()

# Create table
cursor.execute('''
CREATE TABLE IF NOT EXISTS birds (
    id INTEGER PRIMARY KEY,
    species TEXT NOT NULL,
    count INTEGER,
    weight REAL
)
''')

# Insert data
cursor.execute("INSERT INTO birds (species, count, weight) VALUES (?, ?, ?)",
               ("Sparrow", 10, 25.5))
cursor.execute("INSERT INTO birds (species, count, weight) VALUES (?, ?, ?)",
               ("Eagle", 3, 4500.0))

# Insert multiple rows
birds_data = [
    ("Hawk", 5, 1200.0),
    ("Robin", 8, 30.0),
    ("Finch", 6, 15.0)
]
cursor.executemany("INSERT INTO birds (species, count, weight) VALUES (?, ?, ?)",
                   birds_data)

# Commit changes
conn.commit()

# Query data
cursor.execute("SELECT * FROM birds")
for row in cursor.fetchall():
    print(f"ID: {row[0]}, Species: {row[1]}, Count: {row[2]}, Weight: {row[3]}g")

# Query with conditions
cursor.execute("SELECT * FROM birds WHERE count > 5")
print("\nBirds with count > 5:")
for row in cursor.fetchall():
    print(f"{row[1]}: {row[2]}")

# Update data
cursor.execute("UPDATE birds SET count = 12 WHERE species = 'Sparrow'")

# Delete data
cursor.execute("DELETE FROM birds WHERE species = 'Finch'")

# Close connection
conn.close()

16.2 Web Development (Flask, Django, FastAPI)

Web frameworks build web applications and APIs.

Flask: Lightweight, flexible microframework. Django: Full-featured, batteries-included. FastAPI: Modern, fast, async-capable.

Code Example

# Flask example
from flask import Flask, jsonify, request

app = Flask(__name__)

# Sample data
birds = {"Sparrow": 10, "Eagle": 3, "Hawk": 5}

@app.route('/')
def home():
    return "Welcome to Bird API!"

@app.route('/birds')
def get_birds():
    return jsonify(birds)

@app.route('/birds/<species>')
def get_bird(species):
    if species in birds:
        return jsonify({species: birds[species]})
    return jsonify({"error": "Bird not found"}), 404

@app.route('/birds', methods=['POST'])
def add_bird():
    data = request.json
    species = data.get('species')
    count = data.get('count')
    if species and count:
        birds[species] = count
        return jsonify({"message": "Bird added"}), 201
    return jsonify({"error": "Invalid data"}), 400

# Run server
if __name__ == '__main__':
    app.run(debug=True)

# FastAPI example
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World"}

@app.get("/birds/{species}")
def read_bird(species: str):
    return {"species": species, "count": birds.get(species, 0)}

16.3 Data Science (NumPy, Pandas, Matplotlib)

Data science libraries handle numerical operations, data manipulation, and visualization.

NumPy: Numerical computing with arrays. Pandas: Data manipulation and analysis. Matplotlib: Data visualization and plotting.

Code Example

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# ---- NUMPY ----
# Create arrays
arr = np.array([10, 20, 30, 40, 50])
print("Array:", arr)
print("Shape:", arr.shape)
print("Mean:", arr.mean())
print("Sum:", arr.sum())

# 2D array
matrix = np.array([[1, 2, 3], [4, 5, 6]])
print("Matrix:")
print(matrix)

# NumPy operations
print("Squared:", arr ** 2)
print("Square root:", np.sqrt(arr))

# ---- PANDAS ----
# Create DataFrame
data = {
    'Species': ['Sparrow', 'Eagle', 'Hawk', 'Robin'],
    'Count': [10, 3, 5, 8],
    'Weight': [25.5, 4500.0, 1200.0, 30.0]
}
df = pd.DataFrame(data)
print("DataFrame:")
print(df)

# Basic statistics
print("\nStatistics:")
print(df.describe())

# Filtering
print("\nFiltered (Count > 5):")
print(df[df['Count'] > 5])

# Sorting
print("\nSorted by Weight:")
print(df.sort_values('Weight'))

# ---- MATPLOTLIB ----
# Simple plot
plt.figure(figsize=(10, 6))
plt.bar(df['Species'], df['Count'])
plt.title('Bird Counts')
plt.xlabel('Species')
plt.ylabel('Count')
plt.show()

# Custom plot
plt.figure(figsize=(10, 6))
plt.scatter(df['Weight'], df['Count'], s=100)
plt.title('Weight vs Count')
plt.xlabel('Weight (g)')
plt.ylabel('Count')
for i, species in enumerate(df['Species']):
    plt.annotate(species, (df['Weight'][i], df['Count'][i]))
plt.show()

16.4 Machine Learning (Scikit-Learn, TensorFlow, PyTorch)

Machine learning libraries build and train models.

Scikit-Learn: Traditional ML algorithms. TensorFlow: Deep learning framework. PyTorch: Dynamic neural networks.

Code Example

# Scikit-Learn example
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score

# Generate sample data
X, y = make_classification(n_samples=100, n_features=5, random_state=42)

# Split data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

# Train model
model = RandomForestClassifier()
model.fit(X_train, y_train)

# Predict and evaluate
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"Accuracy: {accuracy:.2f}")

# TensorFlow example
import tensorflow as tf

# Simple neural network
model = tf.keras.Sequential([
    tf.keras.layers.Dense(64, activation='relu'),
    tf.keras.layers.Dense(32, activation='relu'),
    tf.keras.layers.Dense(1, activation='sigmoid')
])

# Compile model
model.compile(optimizer='adam',
              loss='binary_crossentropy',
              metrics=['accuracy'])

# Model summary
model.summary()

# PyTorch example (conceptual)
import torch
import torch.nn as nn

class SimpleModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.layer1 = nn.Linear(5, 10)
        self.layer2 = nn.Linear(10, 1)
    
    def forward(self, x):
        x = torch.relu(self.layer1(x))
        x = torch.sigmoid(self.layer2(x))
        return x

# Create model
model = SimpleModel()
print(model)

Real-World Projects

17.1 Beginner Projects (Calculator, Password Generator, To-Do List)

Calculator

Code Example

# Simple Calculator
def calculator():
    print("Simple Calculator")
    print("Operations: +, -, *, /")
    
    try:
        num1 = float(input("Enter first number: "))
        operation = input("Enter operation (+, -, *, /): ")
        num2 = float(input("Enter second number: "))
        
        if operation == '+':
            result = num1 + num2
        elif operation == '-':
            result = num1 - num2
        elif operation == '*':
            result = num1 * num2
        elif operation == '/':
            if num2 == 0:
                print("Error: Division by zero!")
                return
            result = num1 / num2
        else:
            print("Invalid operation!")
            return
        
        print(f"{num1} {operation} {num2} = {result}")
    
    except ValueError:
        print("Error: Invalid input!")

# Run calculator
calculator()

Password Generator

Code Example

import random
import string

def password_generator():
    print("Password Generator")
    
    length = int(input("Enter password length: "))
    use_uppercase = input("Include uppercase? (y/n): ").lower() == 'y'
    use_digits = input("Include digits? (y/n): ").lower() == 'y'
    use_symbols = input("Include symbols? (y/n): ").lower() == 'y'
    
    # Build character set
    chars = string.ascii_lowercase
    if use_uppercase:
        chars += string.ascii_uppercase
    if use_digits:
        chars += string.digits
    if use_symbols:
        chars += string.punctuation
    
    # Generate password
    password = ''.join(random.choice(chars) for _ in range(length))
    print(f"Generated password: {password}")

# Generate multiple passwords
def multiple_passwords():
    count = int(input("How many passwords? "))
    length = int(input("Password length: "))
    for i in range(count):
        password = ''.join(random.choice(string.ascii_letters + string.digits)
                          for _ in range(length))
        print(f"Password {i+1}: {password}")

password_generator()

To-Do List

Code Example

class TodoList:
    def __init__(self):
        self.tasks = []
    
    def add_task(self, task):
        self.tasks.append({"task": task, "done": False})
        print(f"Added: {task}")
    
    def view_tasks(self):
        if not self.tasks:
            print("No tasks!")
            return
        for i, task in enumerate(self.tasks, 1):
            status = "✓" if task["done"] else "○"
            print(f"{i}. [{status}] {task['task']}")
    
    def mark_done(self, index):
        if 0 <= index < len(self.tasks):
            self.tasks[index]["done"] = True
            print(f"Task completed: {self.tasks[index]['task']}")
    
    def delete_task(self, index):
        if 0 <= index < len(self.tasks):
            task = self.tasks.pop(index)
            print(f"Deleted: {task['task']}")
    
    def run(self):
        while True:
            print("\n--- To-Do List ---")
            print("1. Add task")
            print("2. View tasks")
            print("3. Mark task done")
            print("4. Delete task")
            print("5. Exit")
            
            choice = input("Enter choice: ")
            
            if choice == '1':
                task = input("Enter task: ")
                self.add_task(task)
            elif choice == '2':
                self.view_tasks()
            elif choice == '3':
                self.view_tasks()
                index = int(input("Enter task number: ")) - 1
                self.mark_done(index)
            elif choice == '4':
                self.view_tasks()
                index = int(input("Enter task number: ")) - 1
                self.delete_task(index)
            elif choice == '5':
                print("Goodbye!")
                break
            else:
                print("Invalid choice!")

# Run to-do list
todo = TodoList()
todo.run()

17.2 Intermediate Projects (REST API, Web Scraper, Chat Application)

REST API (using Flask)

Code Example

from flask import Flask, jsonify, request

app = Flask(__name__)

# Data store
bird_data = {
    1: {"species": "Sparrow", "count": 10, "weight": 25.5},
    2: {"species": "Eagle", "count": 3, "weight": 4500.0},
    3: {"species": "Hawk", "count": 5, "weight": 1200.0}
}
next_id = 4

# GET all birds
@app.route('/birds', methods=['GET'])
def get_birds():
    return jsonify(bird_data)

# GET single bird
@app.route('/birds/<int:bird_id>', methods=['GET'])
def get_bird(bird_id):
    if bird_id in bird_data:
        return jsonify(bird_data[bird_id])
    return jsonify({"error": "Bird not found"}), 404

# POST new bird
@app.route('/birds', methods=['POST'])
def add_bird():
    global next_id
    data = request.json
    if 'species' not in data or 'count' not in data:
        return jsonify({"error": "Missing required fields"}), 400
    bird_data[next_id] = {
        "species": data['species'],
        "count": data['count'],
        "weight": data.get('weight', 0.0)
    }
    next_id += 1
    return jsonify({"message": "Bird added", "id": next_id - 1}), 201

# PUT update bird
@app.route('/birds/<int:bird_id>', methods=['PUT'])
def update_bird(bird_id):
    if bird_id not in bird_data:
        return jsonify({"error": "Bird not found"}), 404
    data = request.json
    bird_data[bird_id].update(data)
    return jsonify({"message": "Bird updated", "bird": bird_data[bird_id]})

# DELETE bird
@app.route('/birds/<int:bird_id>', methods=['DELETE'])
def delete_bird(bird_id):
    if bird_id not in bird_data:
        return jsonify({"error": "Bird not found"}), 404
    del bird_data[bird_id]
    return jsonify({"message": "Bird deleted"})

if __name__ == '__main__':
    app.run(debug=True)

17.3 Advanced Projects (AI Chatbot, Recommendation System)

AI Chatbot

Code Example

import random
import json
import re

class SimpleChatbot:
    def __init__(self):
        self.responses = {
            "greeting": [
                "Hello! How can I help you?",
                "Hi there! What can I do for you?",
                "Welcome! I'm here to assist you."
            ],
            "bird_question": [
                "Birds are fascinating creatures!",
                "I love learning about birds!",
                "What would you like to know about birds?"
            ],
            "bird_identification": [
                "I can help identify birds based on description.",
                "What color is the bird?",
                "Tell me more about the bird you saw."
            ],
            "unknown": [
                "I'm not sure I understand.",
                "Could you rephrase that?",
                "I'm still learning! Please try again."
            ]
        }
    
    def greet(self):
        return random.choice(self.responses["greeting"])
    
    def identify_bird(self, text):
        bird_keywords = ['sparrow', 'eagle', 'hawk', 'robin', 'finch']
        for bird in bird_keywords:
            if bird in text.lower():
                return f"Ah! You mentioned {bird}. They're wonderful birds!"
        return None
    
    def respond(self, text):
        # Check for greeting
        if any(word in text.lower() for word in ['hello', 'hi', 'hey', 'greetings']):
            return self.greet()
        
        # Check for bird identification
        bird_response = self.identify_bird(text)
        if bird_response:
            return bird_response
        
        # Check for bird questions
        if any(word in text.lower() for word in ['bird', 'feather', 'wing']):
            return random.choice(self.responses["bird_question"])
        
        return random.choice(self.responses["unknown"])
    
    def run(self):
        print("Chatbot: " + self.greet())
        while True:
            user_input = input("You: ")
            if user_input.lower() in ['bye', 'goodbye', 'exit', 'quit']:
                print("Chatbot: Goodbye! Have a great day!")
                break
            response = self.respond(user_input)
            print("Chatbot:", response)

# Run chatbot
# chatbot = SimpleChatbot()
# chatbot.run()

Recommendation System

Code Example

# Simple content-based recommendation
class BirdRecommender:
    def __init__(self):
        self.birds = {
            'Sparrow': {'size': 'small', 'color': 'brown', 'habitat': 'urban'},
            'Eagle': {'size': 'large', 'color': 'brown', 'habitat': 'mountains'},
            'Hawk': {'size': 'medium', 'color': 'brown', 'habitat': 'forests'},
            'Robin': {'size': 'small', 'color': 'red', 'habitat': 'gardens'},
            'Cardinal': {'size': 'medium', 'color': 'red', 'habitat': 'forests'}
        }
    
    def recommend(self, preferences):
        # Find birds matching preferences
        recommendations = []
        for bird, traits in self.birds.items():
            matches = 0
            for key, value in preferences.items():
                if traits.get(key) == value:
                    matches += 1
            if matches > 0:
                recommendations.append((bird, matches))
        
        # Sort by match count
        recommendations.sort(key=lambda x: x[1], reverse=True)
        return [bird for bird, _ in recommendations]
    
    def run(self):
        print("Bird Recommendation System")
        print("Available preferences: size (small/medium/large), color (brown/red), habitat (urban/mountains/forests/gardens)")
        
        preferences = {}
        pref_input = input("Enter preferences (e.g., size=small,color=red): ")
        for item in pref_input.split(','):
            key, value = item.split('=')
            preferences[key.strip()] = value.strip()
        
        recommendations = self.recommend(preferences)
        if recommendations:
            print("Recommended birds:", recommendations)
        else:
            print("No matching birds found")

# Run recommender
recommender = BirdRecommender()
recommender.run()

Testing & Professional Development

18.1 Unit Testing (unittest, pytest)

Unit testing verifies code correctness.

unittest: Built-in testing framework. pytest: More advanced third-party framework. Both support assertions and test discovery.

Code Example

# Using unittest
import unittest

def count_birds(birds):
    return sum(birds.values())

def get_bird(birds, species):
    return birds.get(species, 0)

class TestBirdFunctions(unittest.TestCase):
    def test_count_birds(self):
        bird_data = {"Sparrow": 10, "Eagle": 3, "Hawk": 5}
        self.assertEqual(count_birds(bird_data), 18)
        self.assertEqual(count_birds({}), 0)
    
    def test_get_bird(self):
        bird_data = {"Sparrow": 10, "Eagle": 3}
        self.assertEqual(get_bird(bird_data, "Sparrow"), 10)
        self.assertEqual(get_bird(bird_data, "Hawk"), 0)
    
    def test_invalid_input(self):
        with self.assertRaises(TypeError):
            count_birds(None)

# Run tests
# if __name__ == '__main__':
#     unittest.main()

# Using pytest
def test_count_birds():
    bird_data = {"Sparrow": 10, "Eagle": 3, "Hawk": 5}
    assert count_birds(bird_data) == 18
    assert count_birds({}) == 0

def test_get_bird():
    bird_data = {"Sparrow": 10, "Eagle": 3}
    assert get_bird(bird_data, "Sparrow") == 10
    assert get_bird(bird_data, "Hawk") == 0

# Fixtures with pytest
import pytest

@pytest.fixture
def sample_birds():
    return {"Sparrow": 10, "Eagle": 3, "Hawk": 5}

def test_with_fixture(sample_birds):
    assert count_birds(sample_birds) == 18

18.2 Packaging & Deployment (pip, virtualenv, Docker)

Packaging and deployment tools distribute Python applications.

pip: Package installer. virtualenv: Isolated environment. Docker: Containerization for consistent deployment.

Code Example

# setup.py for packaging
"""
from setuptools import setup, find_packages

setup(
    name="bird-package",
    version="1.0.0",
    packages=find_packages(),
    install_requires=[
        "flask>=2.0.0",
        "pandas>=1.3.0",
        "numpy>=1.21.0"
    ],
    author="Your Name",
    description="A bird management package",
    classifiers=[
        "Programming Language :: Python :: 3",
        "License :: OSI Approved :: MIT License"
    ]
)
"""

# Virtual environment commands
"""
# Create virtual environment
python -m venv venv

# Activate (Windows)
venv\Scripts\activate

# Activate (Mac/Linux)
source venv/bin/activate

# Install packages
pip install flask pandas numpy

# Install from requirements
pip install -r requirements.txt

# Freeze requirements
pip freeze > requirements.txt
"""

# Dockerfile for containerization
"""
FROM python:3.9-slim

WORKDIR /app

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

COPY . .

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

# Docker commands
"""
# Build image
docker build -t bird-app .

# Run container
docker run -p 5000:5000 bird-app
"""

18.3 Open Source Contribution (GitHub workflow, pull requests)

Open source contribution involves collaborating on public projects.

Git workflow: Clone, branch, commit, push. Pull Request: Propose changes to a repository. Code Review: Feedback and approval process.

Code Example

# Git commands
"""
# Clone repository
git clone https://github.com/username/bird-project.git

# Create branch
git checkout -b feature/add-bird-function

# Make changes
# Edit files, add features

# Stage changes
git add .

# Commit with message
git commit -m "Add function to count birds"

# Push branch
git push origin feature/add-bird-function

# Create pull request on GitHub
"""

# Example contribution code
class BirdCalculator:
    def __init__(self):
        self.birds = {}
    
    def add_bird(self, species, count):
        """Add or update bird count."""
        self.birds[species] = self.birds.get(species, 0) + count
    
    def get_total(self):
        """Return total number of birds."""
        return sum(self.birds.values())
    
    def get_species(self):
        """Return list of bird species."""
        return list(self.birds.keys())

# Contribution guidelines
"""
# Contributing to Bird-Project

## How to Contribute
1. Fork the repository
2. Create a feature branch
3. Write tests for your changes
4. Submit a pull request

## Coding Standards
- Follow PEP 8
- Add docstrings
- Include unit tests
- Update documentation
"""

Career Readiness

19.1 Portfolio Development

A portfolio showcases your Python projects and skills.

Include diverse projects showing different skills. Organize with clear README files. Demonstrate problem-solving ability.

Code Example

# Example README.md for portfolio project
"""
# Bird Management System

A Python application for tracking bird populations in different habitats.

## Features
- Add and update bird species
- Track population statistics
- Generate reports
- Export data to CSV

## Technologies
- Python 3.9+
- Flask for web interface
- SQLite for data storage
- Pandas for data analysis
- Matplotlib for visualizations

## Installation
```bash
git clone https://github.com/username/bird-management
cd bird-management
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt

Usage

from bird_system import BirdManager

manager = BirdManager()
manager.add_bird("Sparrow", 10)
manager.add_bird("Eagle", 3)
print(manager.get_report())

Demo

[Link to live demo]

Contact

Portfolio organization

“””
Portfolio Structure:
├── projects/
│ ├── bird-management/
│ │ ├── README.md
│ │ ├── src/
│ │ ├── tests/
│ │ └── requirements.txt
│ ├── data-analysis/
│ └── machine-learning/
├── README.md
└── contact.md
“””


---

## 19.2 Technical Interviews

**What They Are**

Technical interviews assess Python knowledge and problem-solving.

**Detailed Explanation**

Practice common questions, explain your thought process, write clean code, and test edge cases.

**Code Example**

```python
# Common interview questions

# 1. Reverse a string
def reverse_string(s):
    return s[::-1]

# 2. Check if palindrome
def is_palindrome(s):
    s = s.lower().replace(" ", "")
    return s == s[::-1]

# 3. Find duplicates in list
def find_duplicates(arr):
    seen = set()
    duplicates = []
    for item in arr:
        if item in seen:
            duplicates.append(item)
        else:
            seen.add(item)
    return duplicates

# 4. Fibonacci
def fibonacci(n):
    if n <= 1:
        return n
    a, b = 0, 1
    for _ in range(2, n + 1):
        a, b = b, a + b
    return b

# 5. FizzBuzz
def fizzbuzz(n):
    for i in range(1, n + 1):
        if i % 15 == 0:
            print("FizzBuzz")
        elif i % 3 == 0:
            print("Fizz")
        elif i % 5 == 0:
            print("Buzz")
        else:
            print(i)

# 6. Binary Search
def binary_search(arr, target):
    left, right = 0, len(arr) - 1
    while left <= right:
        mid = (left + right) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return -1

# 7. Two Sum
def two_sum(nums, target):
    seen = {}
    for i, num in enumerate(nums):
        complement = target - num
        if complement in seen:
            return [seen[complement], i]
        seen[num] = i
    return []

19.3 Resume Building

A resume highlights your Python skills and experience.

Include technical skills, relevant projects, work experience, and education. Quantify achievements and use action verbs.

Code Example

# Resume template example
"""
# John Doe
Python Developer | Data Scientist
Email: john@example.com | LinkedIn: linkedin.com/in/johndoe | GitHub: github.com/johndoe

## Summary
Experienced Python developer with 5+ years of experience in data science and
web development. Passionate about building scalable applications and solving
complex problems.

## Technical Skills
- Languages: Python, SQL, JavaScript
- Frameworks: Django, Flask, FastAPI
- Libraries: Pandas, NumPy, Scikit-learn, TensorFlow
- Tools: Git, Docker, AWS, Jupyter

## Experience
### Senior Python Developer | Tech Company (2021-Present)
- Built RESTful APIs serving 10k+ daily requests
- Reduced response time by 40% through optimization
- Led team of 5 developers on 3 major projects

### Python Developer | Startup (2018-2021)
- Developed data pipelines processing 1M+ records daily
- Implemented ML models achieving 92% accuracy
- Created dashboards for real-time monitoring

## Projects
### Bird Management System
- Python, Flask, SQLite, Pandas
- Complete bird tracking system with reporting features
- Deployed on AWS with 99.9% uptime

## Education
### MS in Computer Science, University Name (2016-2018)
### BS in Computer Science, University Name (2012-2016)
"""

19.4 Freelancing and Employment

Freelancing and employment are career paths for Python developers.

Freelancing involves working for multiple clients independently. Employment involves working for a company. Both require strong communication and problem-solving skills.

Code Example

# Freelance proposal template
"""
Project Proposal: Bird Data Analysis Platform

Client: ABC Wildlife Foundation

## Executive Summary
Development of a comprehensive bird tracking and analysis platform
using Python, Django, and advanced data visualization tools.

## Project Scope
- Build interactive dashboard for bird sightings
- Implement data import from CSV and API sources
- Create automated reporting system
- Provide real-time analytics and visualizations

## Technical Approach
- Backend: Python/Django REST Framework
- Database: PostgreSQL
- Frontend: React.js
- Deployment: AWS

## Timeline
- Phase 1: Requirements & Design (2 weeks)
- Phase 2: Development (8 weeks)
- Phase 3: Testing & Deployment (2 weeks)
- Phase 4: Training & Handover (1 week)

## Cost Estimate
- Development: $15,000
- Deployment & Maintenance: $2,000/year

## Portfolio
- Bird Management System (link)
- Wildlife Data Platform (link)

## References
Available upon request
"""

# Job application checklist
"""
## Job Application Checklist

1. Research the company and role
2. Customize resume and cover letter
3. Prepare for technical interview
4. Practice common coding questions
5. Review data structures and algorithms
6. Prepare examples of past projects
7. Practice system design questions
8. Prepare questions to ask the interviewer
9. Follow up after interview
10. Negotiate offer

## Interview Preparation Resources
- LeetCode: Practice coding problems
- HackerRank: Coding challenges
- Glassdoor: Company reviews and interview questions
- LinkedIn: Professional networking
- GitHub: Showcase your projects
"""

# Networking tips
"""
## Networking for Python Developers

1. Attend Python meetups and conferences
2. Contribute to open source projects
3. Share projects on GitHub
4. Write blog posts about Python
5. Connect on LinkedIn
6. Join Python Slack/Discord communities
7. Participate in coding competitions
8. Offer to speak at local meetups
9. Create tutorials and guides
10. Help others in Python forums
"""

This comprehensive Python guide covers all the topics from the roadmap with clear explanations, practical code examples, and real-world applications. Each section is designed to be standalone while building on previous concepts, making it suitable for beginners and intermediate learners alike.

Scroll to Top