Complete Python Tutorial with Usage Examples

Table of Contents

1. Introduction to Python

Python is a high-level, interpreted, general-purpose programming language. Created by Guido van Rossum and first released in 1991, Python's design philosophy emphasizes code readability with its notable use of significant indentation.

Python is known for its simplicity, versatility, and vast ecosystem of libraries, making it popular for:

Key Features of Python:

Tip for Practice: Open a terminal or command prompt and type `python` (or `python3`) to enter the interactive Python interpreter. You can type Python code directly there to experiment! For longer examples, use a code editor (VS Code, PyCharm).

2. Installing Python

Python 3 is the current and recommended version. Python 2 is deprecated and should not be used for new projects.

A. Check if Python is Installed:

Open your terminal/command prompt and type:

python3 --version
# Output example: Python 3.9.7

If you see a version starting with 3, you're good. If not, or if it shows Python 2.x, proceed with installation.

B. Installation Methods:

C. Using `pip` (Python Package Installer):

`pip` is the standard package manager for Python, used to install external libraries.

pip3 install numpy      # Install the NumPy library
pip3 install pandas requests # Install multiple libraries
pip3 list               # List installed packages
pip3 uninstall numpy    # Uninstall a package

3. Hello World Program

The classic first program in Python.

A. Create the File:

Create a file named `hello.py` using any text editor.

# hello.py
print("Hello, World!") # Prints "Hello, World!" to the console

B. Run the Program:

Open your terminal/command prompt, navigate to the directory where you saved `hello.py`, and run:

python3 hello.py

Expected Output:

Hello, World!

4. Python Basics (Syntax, Comments, Indentation)

A. Indentation (Crucial!):

Python uses **indentation** to define code blocks (e.g., within `if` statements, loops, functions, classes), unlike other languages that use curly braces `{}`. Consistent indentation (usually 4 spaces) is mandatory.

# Correct indentation
if True:
    print("This line is indented by 4 spaces.")
    print("This is also part of the 'if' block.")
else:
    print("This is part of the 'else' block.")
print("This line is outside the 'if/else' block.")
# Incorrect indentation (will cause an IndentationError)
if True:
    print("This is fine.")
  print("This line has inconsistent indentation and will error.")

B. Comments:

Used to explain code. Ignored by the interpreter.

C. Statements & Lines:

5. Variables & Data Types

Variables are containers for storing data. Python is dynamically typed, meaning you don't declare the type of a variable; the interpreter infers it at runtime.

A. Variable Assignment:

name = "Alice"     # String
age = 30           # Integer
height = 1.75      # Float
is_student = True  # Boolean

# Reassigning changes type
age = "thirty" # Now 'age' is a string

B. Built-in Data Types:

C. Checking Type:

print(type(name)) # <class 'str'>
print(type(age))  # <class 'int'>

6. Operators

Symbols that perform operations on values and variables.

7. Control Flow (Conditionals & Loops)

Control flow statements dictate the order in which instructions are executed.

A. Conditionals (`if`, `elif`, `else`):

temperature = 25
if temperature > 30:
    print("It's hot!")
elif temperature > 20: # 'elif' is short for 'else if'
    print("It's warm.")
else:
    print("It's cool.")
# Output: It's warm.

Ternary Operator (Conditional Expression): (Shorthand for simple if-else).

score = 75
result = "Pass" if score >= 60 else "Fail"
print(result) # Output: Pass

B. Loops:

8. Functions

Functions are reusable blocks of code that perform a specific task. Defined using the `def` keyword.

9. Data Structures (Lists, Tuples, Sets, Dictionaries)

Python offers powerful built-in data structures.

A. Lists (`list`):

Ordered, mutable (changeable) collections. Enclosed in square brackets `[]`.

my_list = [10, "hello", 3.14, True]
print(my_list[0])    # 10 (access by index, 0-based)
print(my_list[-1])   # True (last element)
my_list[1] = "world" # Modify
my_list.append(False) # Add to end
my_list.insert(1, "new_item") # Insert at specific index
my_list.remove("hello") # Remove by value
print(my_list) # [10, 'new_item', 3.14, True, False]
print(len(my_list)) # 5
# Slicing:
print(my_list[1:3]) # ['new_item', 3.14] (elements from index 1 up to (but not including) 3)
print(my_list[:2])  # [10, 'new_item'] (from start to index 2)
print(my_list[2:])  # [3.14, True, False] (from index 2 to end)

B. Tuples (`tuple`):

Ordered, immutable (unchangeable) collections. Enclosed in parentheses `()`.

my_tuple = (1, "two", 3.0)
# my_tuple[0] = 5 # Error: Tuples are immutable
print(my_tuple[1]) # two
print(my_tuple.count(1)) # Count occurrences of 1
print(my_tuple.index("two")) # Get index of "two"

C. Sets (`set`):

Unordered, mutable collections of unique elements. Enclosed in curly braces `{}` (or `set()` for empty set).

my_set = {1, 2, 3, 2, 4} # Duplicate '2' is ignored
print(my_set) # {1, 2, 3, 4} (order may vary)
my_set.add(5)
my_set.remove(1)
print(my_set) # {2, 3, 4, 5}
# Set operations:
set1 = {1, 2, 3}
set2 = {3, 4, 5}
print(set1.union(set2))        # {1, 2, 3, 4, 5}
print(set1.intersection(set2)) # {3}
print(set1.difference(set2))   # {1, 2}

D. Dictionaries (`dict`):

Unordered, mutable collections of key-value pairs. Keys must be unique and immutable (strings, numbers, tuples). Enclosed in curly braces `{}`.

my_dict = {"name": "Alice", "age": 30, "city": "New York"}
print(my_dict["name"])    # Alice (access by key)
my_dict["age"] = 31       # Modify value
my_dict["country"] = "USA" # Add new key-value pair
del my_dict["city"]       # Delete by key
print(my_dict)            # {'name': 'Alice', 'age': 31, 'country': 'USA'}
print(my_dict.keys())     # dict_keys(['name', 'age', 'country'])
print(my_dict.values())   # dict_values(['Alice', 31, 'USA'])
print(my_dict.items())    # dict_items([('name', 'Alice'), ('age', 31), ('country', 'USA')])

# Iterate through a dictionary
for key, value in my_dict.items():
    print(f"{key}: {value}")

10. Strings

Strings are sequences of characters. They are immutable (cannot be changed after creation).

A. Creation:

str1 = "Hello, Python"
str2 = 'Another string'
str3 = """This is a
multi-line string"""

B. Concatenation:

full_name = "John" + " " + "Doe" # John Doe
message = "Count: " + str(10) # Convert number to string for concatenation

C. String Formatting:

D. Common String Methods:

text = "python programming"
print(text.upper())           # PYTHON PROGRAMMING
print(text.capitalize())      # Python programming
print(text.title())           # Python Programming
print(text.replace("python", "Java")) # Java programming
print(text.split(" "))        # ['python', 'programming']
print("  hello  ".strip())    # "hello" (removes whitespace)
print(text.startswith("py"))  # True
print(text.endswith("ing"))   # True
print("abc".join(["1", "2"])) # 1abc2

E. String Slicing:

s = "abcdefg"
print(s[0])    # a (first character)
print(s[1:4])  # bcd (from index 1 up to but not including 4)
print(s[:3])   # abc (from start to index 3)
print(s[4:])   # efg (from index 4 to end)
print(s[::2])  # aceg (every 2nd character)
print(s[::-1]) # gfedcba (reverse string)

11. Object-Oriented Programming (OOP)

Python fully supports object-oriented programming (OOP) paradigms. OOP allows you to structure your programs around "objects" rather than actions and data rather than logic.

A. Classes and Objects:

# Define a class 'Car'
class Car:
    # Class attribute (shared by all instances)
    wheels = 4

    # Constructor method: Called when a new object is created
    def __init__(self, make, model, year):
        # Instance attributes (unique to each object)
        self.make = make
        self.model = model
        self.year = year
        self.is_running = False

    # Instance method (behavior)
    def start(self):
        if not self.is_running:
            self.is_running = True
            print(f"{self.make} {self.model} starting...")
        else:
            print(f"{self.make} {self.model} is already running.")

    def stop(self):
        if self.is_running:
            self.is_running = False
            print(f"{self.make} {self.model} stopping.")
        else:
            print(f"{self.make} {self.model} is already stopped.")

    def display_info(self):
        print(f"Make: {self.make}, Model: {self.model}, Year: {self.year}, Running: {self.is_running}")

# Create objects (instances) of the Car class
my_car = Car("Toyota", "Camry", 2020) # Calls the __init__ constructor
another_car = Car("Honda", "Civic", 2022)

# Access attributes and call methods on objects
print(f"My car has {my_car.wheels} wheels.") # Access class attribute
my_car.start()          # Output: Toyota Camry starting...
my_car.display_info()   # Output: Make: Toyota, Model: Camry, Year: 2020, Running: True

another_car.start()
another_car.stop()
another_car.display_info()

B. Inheritance:

Allows a class (child/subclass) to inherit attributes and methods from another class (parent/superclass).

class ElectricCar(Car): # ElectricCar inherits from Car
    def __init__(self, make, model, year, battery_size_kwh):
        super().__init__(make, model, year) # Call parent class constructor
        self.battery_size_kwh = battery_size_kwh

    def charge(self):
        print(f"{self.make} {self.model} is charging its {self.battery_size_kwh} kWh battery.")

    # Override a parent method
    def start(self):
        print(f"Electric {self.make} {self.model} silently powering on...")
        self.is_running = True

my_ev = ElectricCar("Tesla", "Model 3", 2023, 75)
my_ev.display_info() # Inherited from Car
my_ev.start()        # Overridden method
my_ev.charge()       # New method specific to ElectricCar

C. Polymorphism:

Means "many forms." Allows objects of different classes to be treated as objects of a common superclass, or respond to the same method call in different ways.

def start_any_car(car_object):
    car_object.start() # Calls the start() method specific to the object's class

start_any_car(my_car) # Calls Car's start()
start_any_car(my_ev)  # Calls ElectricCar's start() (polymorphism)

D. Encapsulation (via Naming Conventions):

Bundling data and methods within a class. Python doesn't have strict `private` keywords like Java, but uses conventions:

class Account:
    def __init__(self, balance):
        self._balance = balance # Weakly private, convention
        self.__account_number = "12345" # Name mangled

    def get_balance(self):
        return self._balance

# Accessing
acc = Account(100)
print(acc.get_balance()) # 100
# print(acc.__account_number) # Error: AttributeError
# print(acc._Account__account_number) # Can still be accessed, but discouraged

12. Modules & Packages

Python allows you to organize code into reusable files (modules) and directories of modules (packages).

A. Modules:

A module is simply a Python file (`.py`) containing Python code (functions, classes, variables).

# my_math.py
PI = 3.14159

def add(a, b):
    return a + b

def subtract(a, b):
    return a - b
# main.py
import my_math # Import the entire module
from my_math import add, PI # Import specific items

print(my_math.PI)      # Access through module name
print(my_math.add(5, 3))

print(PI)              # Access directly
print(add(10, 4))

B. Packages:

A package is a directory containing Python modules and an `__init__.py` file (even if empty, it signifies the directory is a Python package). Packages allow hierarchical organization of modules.

my_project/
├── main.py
├── calculations/
│   ├── __init__.py
│   ├── basic.py
│   └── advanced.py
└── data/
    └── __init__.py
    └── loader.py
# calculations/basic.py
def add(a, b):
    return a + b
# calculations/advanced.py
def power(base, exp):
    return base ** exp
# main.py
from calculations import basic
from calculations.advanced import power

print(basic.add(10, 20))    # Output: 30
print(power(2, 3))          # Output: 8
**Importing from `__init__.py`:** You can also import modules or functions directly into a package's `__init__.py` file to simplify imports (e.g., `from . import basic` in `calculations/__init__.py`, then `from calculations import add` in `main.py`).

13. File I/O (Input/Output)

Reading from and writing to files.

A. Reading from a file:

# Example: Read entire file
try:
    with open("my_document.txt", "r") as file:
        content = file.read()
        print("File content:\n", content)
except FileNotFoundError:
    print("Error: my_document.txt not found.")

# Example: Read line by line
try:
    with open("my_document.txt", "r") as file:
        for line in file:
            print("Line:", line.strip()) # .strip() removes newline characters
except FileNotFoundError:
    print("Error: my_document.txt not found.")

B. Writing to a file:

# Overwrite file: "w" mode
with open("new_output.txt", "w") as file:
    file.write("This is the first line.\n")
    file.write("This is the second line.\n")
print("Content written to new_output.txt (overwritten).")

# Append to file: "a" mode
with open("new_output.txt", "a") as file:
    file.write("This line is appended.\n")
print("Content appended to new_output.txt.")
**`with open(...)` (Context Manager):** This is the recommended way to handle files. It ensures that the file is automatically closed, even if errors occur.
**File Modes:**

14. Exception Handling

Mechanism to handle runtime errors (exceptions) gracefully, preventing the program from crashing.

try:
    num1 = int(input("Enter a number: "))
    num2 = int(input("Enter another number: "))
    result = num1 / num2
    print(f"Result: {result}")
except ValueError:
    print("Invalid input! Please enter a valid number.")
except ZeroDivisionError:
    print("Error: Cannot divide by zero.")
except Exception as e: # Catch any other unexpected exceptions
    print(f"An unexpected error occurred: {e}")
else:
    print("Division successful.")
finally:
    print("Execution of the division attempt is complete.")

Raising Custom Exceptions:

class CustomError(Exception):
    "Raised when a specific custom error condition occurs"
    pass

def validate_age(age):
    if age < 0:
        raise ValueError("Age cannot be negative.")
    if age < 18:
        raise CustomError("User must be 18 or older.")
    print("Age is valid.")

try:
    validate_age(15)
except ValueError as ve:
    print(f"Validation Error (ValueError): {ve}")
except CustomError as ce:
    print(f"Validation Error (CustomError): {ce}")

15. Virtual Environments

Virtual environments create isolated Python installations for your projects. This prevents dependency conflicts between different projects.

Usage:

# 1. Create a project directory
mkdir my_python_project
cd my_python_project

# 2. Create a virtual environment (named 'venv' is common)
python3 -m venv venv

# 3. Activate the virtual environment
# On Linux/macOS:
source venv/bin/activate
# On Windows (cmd.exe):
venv\Scripts\activate.bat
# On Windows (PowerShell):
venv\Scripts\Activate.ps1

# 4. (After activation) Install packages within the virtual environment
pip install Flask requests pandas

# 5. (Optional) Deactivate the virtual environment when done
deactivate

# To re-activate later, just run step 3 again from the project directory.

When the virtual environment is activated, your terminal prompt usually changes (e.g., `(venv) my_python_project $`). Any `pip install` commands will install packages into this isolated environment.

16. Comprehensions (List, Dictionary, Set)

Provide a concise way to create lists, dictionaries, or sets based on existing iterables.

A. List Comprehensions:

# Basic list comprehension: [expression for item in iterable]
squares = [x**2 for x in range(10)]
print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# With a condition: [expression for item in iterable if condition]
even_squares = [x**2 for x in range(10) if x % 2 == 0]
print(even_squares) # [0, 4, 16, 36, 64]

# Nested comprehension:
matrix = [[1, 2], [3, 4]]
flat_list = [num for row in matrix for num in row]
print(flat_list) # [1, 2, 3, 4]

B. Dictionary Comprehensions:

# {key_expression: value_expression for item in iterable}
sq_dict = {x: x**2 for x in range(5)}
print(sq_dict) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# With conditions:
even_dict = {x: x**2 for x in range(10) if x % 2 == 0}
print(even_dict) # {0: 0, 2: 4, 4: 16, 6: 36, 8: 64}

C. Set Comprehensions:

# {expression for item in iterable}
unique_chars = {char for char in "hello world" if char.isalpha()}
print(unique_chars) # {'h', 'e', 'l', 'o', 'w', 'r', 'd'} (order not guaranteed)

17. Iterators & Generators

A. Iterators:

An object that represents a stream of data. It has a `__iter__()` method (returns the iterator itself) and a `__next__()` method (returns the next item in the sequence). Many built-in types (lists, tuples, strings) are iterable.

my_list = [1, 2, 3]
my_iterator = iter(my_list) # Get an iterator from an iterable

print(next(my_iterator)) # 1
print(next(my_iterator)) # 2
print(next(my_iterator)) # 3
# print(next(my_iterator)) # Raises StopIteration Error

B. Generators:

Functions that return an iterator. They are defined like normal functions but use the `yield` keyword instead of `return` to produce a sequence of values. Generators produce values on-the-fly (lazy evaluation), saving memory.

def countdown(n):
    print("Starting countdown...")
    while n > 0:
        yield n # Yields a value, pauses execution, resumes from here next time
        n -= 1
    print("Countdown finished!")

# Create a generator object
counter = countdown(3)

print(next(counter)) # Output: Starting countdown... \n 3
print(next(counter)) # Output: 2
print(next(counter)) # Output: 1
# print(next(counter)) # Output: Countdown finished! \n StopIteration Error

Generators are ideal for handling large datasets that don't fit into memory.

18. Decorators

Decorators are a powerful way to modify or enhance the behavior of functions or methods without permanently altering their code. They are essentially functions that take another function as an argument and return a new (decorated) function.

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"--- Calling {func.__name__} ---")
        result = func(*args, **kwargs) # Call the original function
        print(f"--- Finished {func.__name__} ---")
        return result
    return wrapper

@my_decorator # This is equivalent to: say_hello = my_decorator(say_hello)
def say_hello(name):
    print(f"Hello, {name}!")

@my_decorator
def calculate_sum(a, b):
    print(f"Calculating sum of {a} and {b}")
    return a + b

say_hello("Alice")
# Output:
# --- Calling say_hello ---
# Hello, Alice!
# --- Finished say_hello ---

sum_result = calculate_sum(10, 20)
print(f"Sum result: {sum_result}")
# Output:
# --- Calling calculate_sum ---
# Calculating sum of 10 and 20
# --- Finished calculate_sum ---
# Sum result: 30
**Common Use Cases for Decorators:** Logging, timing function execution, authentication/authorization checks, caching, rate limiting.

19. Advanced Functions (*args, **kwargs)

These special syntaxes allow functions to accept a variable number of arguments.

20. Regular Expressions

Regular expressions (regex) are sequences of characters that define a search pattern. Python's `re` module provides full support for regular expressions.

import re

text = "The quick brown fox jumps over the lazy dog."

# Search for a pattern
match = re.search(r"fox", text)
if match:
    print(f"Found 'fox' at index {match.start()} to {match.end()}")

# Find all occurrences of a pattern
all_words = re.findall(r"\b\w+\b", text) # \b word boundary, \w+ one or more word characters
print(f"All words: {all_words}")

# Replace patterns
new_text = re.sub(r"lazy", "active", text)
print(f"Replaced text: {new_text}")

# Check for email pattern
email_pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
email1 = "test@example.com"
email2 = "invalid-email"
print(f"'{email1}' is valid: {bool(re.match(email_pattern, email1))}")
print(f"'{email2}' is valid: {bool(re.match(email_pattern, email2))}")

21. Working with JSON

JSON (JavaScript Object Notation) is a lightweight data-interchange format. Python has built-in support for JSON via the `json` module.

import json

# Python dictionary
data = {
    "name": "Alice",
    "age": 28,
    "is_student": True,
    "courses": ["History", "Math", "Science"],
    "address": None
}

# Convert Python dict to JSON string (Serialization)
json_string = json.dumps(data, indent=4) # indent for pretty printing
print("Python dict converted to JSON:\n", json_string)

# Convert JSON string to Python dict (Deserialization)
json_data = '{"product": "Laptop", "price": 1200, "in_stock": true}'
python_dict = json.loads(json_data)
print("JSON string converted to Python dict:", python_dict)
print(f"Product: {python_dict['product']}, Price: {python_dict['price']}")

# Write JSON to a file
with open("data.json", "w") as f:
    json.dump(data, f, indent=4)
print("\nData written to data.json")

# Read JSON from a file
with open("data.json", "r") as f:
    loaded_data = json.load(f)
print("Data loaded from data.json:", loaded_data)

22. Web Development Frameworks (Flask, Django)

Python has robust frameworks for building web applications.

A. Flask:

A micro web framework. Lightweight and flexible, good for smaller APIs and web apps.

# Install Flask: pip install Flask

# app.py
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/')
def home():
    return "Hello, Flask Web App!"

@app.route('/greet/<name>')
def greet(name):
    return f"Hello, {name}!"

@app.route('/api/data', methods=['POST'])
def process_data():
    if request.is_json:
        data = request.get_json()
        return jsonify({"received": data, "status": "success"}), 200
    return jsonify({"error": "Request must be JSON"}), 400

if __name__ == '__main__':
    # For local development:
    # Set FLASK_APP=app.py
    # flask run
    # OR
    app.run(debug=True, port=5000)

To run: Save as `app.py`. Open terminal in directory, `python3 app.py`. Then open `http://127.0.0.1:5000/` in browser.

B. Django:

A high-level web framework that encourages rapid development and clean, pragmatic design. Full-featured, good for complex applications.

# Install Django: pip install Django

# Basic steps to create a Django project (CLI):
# django-admin startproject myproject
# cd myproject
# python manage.py startapp myapp
# (Edit myproject/settings.py, myapp/views.py, myapp/urls.py, etc.)
# python manage.py migrate
# python manage.py runserver

23. Data Science & ML Libraries (NumPy, Pandas, Scikit-learn)

Python is a dominant language in data science due to its powerful libraries.

A. NumPy:

Fundamental package for numerical computing, providing support for large, multi-dimensional arrays and matrices, along with mathematical functions to operate on these arrays.

# Install: pip install numpy

import numpy as np

# Create NumPy array
arr = np.array([1, 2, 3, 4, 5])
print("Array:", arr) # [1 2 3 4 5]
print("Type:", type(arr)) # <class 'numpy.ndarray'>

# Array operations (vectorized)
arr_squared = arr ** 2
print("Squared:", arr_squared) # [ 1  4  9 16 25]
print("Sum:", np.sum(arr)) # 15

# Multi-dimensional array (matrix)
matrix = np.array([[1, 2], [3, 4]])
print("Matrix:\n", matrix)
print("Shape:", matrix.shape) # (2, 2)

B. Pandas:

A powerful data manipulation and analysis library, built on NumPy. Provides DataFrames (like spreadsheets or SQL tables).

# Install: pip install pandas

import pandas as pd

# Create a DataFrame
data = {
    'Name': ['Alice', 'Bob', 'Charlie'],
    'Age': [25, 30, 35],
    'City': ['NY', 'LA', 'SF']
}
df = pd.DataFrame(data)
print("DataFrame:\n", df)

# Access columns
print("\nNames:", df['Name'])

# Filter data
filtered_df = df[df['Age'] > 28]
print("\nFiltered by Age > 28:\n", filtered_df)

# Read from CSV/Excel
# df_csv = pd.read_csv('data.csv')
# df_excel = pd.read_excel('data.xlsx')

C. Scikit-learn:

A comprehensive machine learning library, offering various classification, regression, clustering, and dimensionality reduction algorithms.

# Install: pip install scikit-learn

from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
import numpy as np

# Sample data
X = np.array([[1], [2], [3], [4], [5]]) # Features
y = np.array([2, 4, 5, 4, 5])           # Target

# Split data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

# Create a Linear Regression model
model = LinearRegression()

# Train the model
model.fit(X_train, y_train)

# Make predictions
y_pred = model.predict(X_test)
print(f"Predictions: {y_pred}")

# Evaluate the model
mse = mean_squared_error(y_test, y_pred)
print(f"Mean Squared Error: {mse}")

24. Best Practices

Python: Your Versatile Programming Companion!

Python's simplicity, extensive libraries, and vast community make it an ideal language for beginners and experienced developers alike. From web development and data science to automation and AI, Python's versatility is unparalleled. Continuous learning, hands-on practice, and adherence to best practices will empower you to build powerful and elegant solutions with Python.