Python

Python Tutorial

Master the most versatile programming language from basics to advanced

1 What is Python?

Python is a high-level, interpreted programming language known for its simplicity and readability. Created by Guido van Rossum in 1991, it's one of the most popular languages for web development, data science, AI/ML, automation, and more.

Python Use Cases
Web Dev
Django, Flask
Data Science
Pandas, NumPy
AI/ML
TensorFlow, PyTorch
Automation
Scripts, Bots

🔑 Why Learn Python?

Easy to Learn: Clean, readable syntax that's beginner-friendly
Versatile: Used in web, data science, AI, automation, and more
Large Community: Extensive libraries and active support
High Demand: One of the most sought-after programming skills

Python vs Other Languages

Python
# Python - Simple and readable
def greet(name):
    return f"Hello, {name}!"

print(greet("World"))

# Output: Hello, World!
Java (Comparison)
// Java - More verbose
public class Main {
    public static String greet(String name) {
        return "Hello, " + name + "!";
    }
    
    public static void main(String[] args) {
        System.out.println(greet("World"));
    }
}

2 Installation & Setup

Python can be installed from python.org or through package managers. Most systems come with Python pre-installed.

Terminal Commands
# Check if Python is installed
python --version
# or
python3 --version

# Install on Ubuntu/Debian
sudo apt update
sudo apt install python3 python3-pip

# Install on macOS (using Homebrew)
brew install python3

# Install on Windows
# Download from python.org and run installer
# Make sure to check "Add Python to PATH"

# Create a virtual environment (recommended)
python3 -m venv myproject
source myproject/bin/activate  # Linux/Mac
myproject\Scripts\activate     # Windows

# Install packages with pip
pip install numpy pandas requests

💡 IDEs for Python

VS Code - Free, lightweight, great extensions
PyCharm - Full-featured Python IDE
Jupyter Notebook - Interactive coding for data science
IDLE - Comes with Python installation

3 Your First Python Program

Let's write your first Python program! Create a file called hello.py and add the following code.

hello.py
# This is a comment - Python ignores it
print("Hello, World!")

# Variables don't need type declarations
name = "Alice"
age = 25

# f-strings for string formatting
print(f"My name is {name} and I am {age} years old.")

# Simple math
result = 10 + 5 * 2
print(f"10 + 5 * 2 = {result}")
▶ OUTPUT
Hello, World! My name is Alice and I am 25 years old. 10 + 5 * 2 = 20
Running Python
# Run from terminal
python hello.py

# Or use interactive mode
python
>>> print("Hello!")
Hello!
>>> exit()

4 Variables & Data Types

Python is dynamically typed - you don't need to declare variable types. Python figures it out automatically.

Python Data Types
int
42, -10
float
3.14, -0.5
str
"hello"
bool
True, False
Python - Variables
# Numbers
age = 25              # int
price = 19.99         # float
big_num = 1_000_000   # underscores for readability

# Strings
name = "Alice"
message = 'Hello, World!'
multi_line = """This is a
multi-line string"""

# Boolean
is_active = True
is_logged_in = False

# None (null value)
result = None

# Check types
print(type(age))       # <class 'int'>
print(type(price))     # <class 'float'>
print(type(name))      # <class 'str'>
print(type(is_active)) # <class 'bool'>

# Type conversion
num_str = "42"
num_int = int(num_str)     # String to int
num_float = float(num_str) # String to float
back_to_str = str(42)      # Int to string

# Multiple assignment
x, y, z = 1, 2, 3
a = b = c = 0

⚠️ Variable Naming Rules

• Must start with letter or underscore
• Can contain letters, numbers, underscores
• Case-sensitive (age ≠ Age ≠ AGE)
• Cannot use reserved words (if, for, class, etc.)

5 Operators

Python supports various operators for arithmetic, comparison, logical operations, and more.

Python - Operators
# Arithmetic Operators
a, b = 10, 3
print(a + b)   # 13  (Addition)
print(a - b)   # 7   (Subtraction)
print(a * b)   # 30  (Multiplication)
print(a / b)   # 3.333... (Division - always float)
print(a // b)  # 3   (Floor Division - integer)
print(a % b)   # 1   (Modulus - remainder)
print(a ** b)  # 1000 (Exponent - 10^3)

# Comparison Operators
x, y = 5, 10
print(x == y)  # False (Equal)
print(x != y)  # True  (Not equal)
print(x < y)   # True  (Less than)
print(x > y)   # False (Greater than)
print(x <= y)  # True  (Less than or equal)
print(x >= y)  # False (Greater than or equal)

# Logical Operators
print(True and False)  # False
print(True or False)   # True
print(not True)        # False

# Assignment Operators
n = 10
n += 5   # n = n + 5  → 15
n -= 3   # n = n - 3  → 12
n *= 2   # n = n * 2  → 24
n //= 4  # n = n // 4 → 6

# Identity Operators
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(a == b)     # True  (same values)
print(a is b)     # False (different objects)
print(a is c)     # True  (same object)

# Membership Operators
fruits = ["apple", "banana", "cherry"]
print("apple" in fruits)     # True
print("grape" not in fruits) # True

6 Strings

Strings in Python are immutable sequences of characters. Python provides powerful string manipulation methods.

Python - Strings
# String creation
single = 'Hello'
double = "World"
multi = """Multi
line
string"""

# String concatenation
full = single + " " + double  # "Hello World"

# String formatting
name = "Alice"
age = 25
# f-strings (recommended - Python 3.6+)
msg1 = f"Name: {name}, Age: {age}"
# .format() method
msg2 = "Name: {}, Age: {}".format(name, age)
# % operator (old style)
msg3 = "Name: %s, Age: %d" % (name, age)

# String indexing & slicing
text = "Python"
print(text[0])     # 'P'  (first character)
print(text[-1])    # 'n'  (last character)
print(text[0:3])   # 'Pyt' (slice)
print(text[::2])   # 'Pto' (every 2nd char)
print(text[::-1])  # 'nohtyP' (reversed)

# String methods
s = "  Hello, World!  "
print(s.strip())       # "Hello, World!" (remove whitespace)
print(s.lower())       # "  hello, world!  "
print(s.upper())       # "  HELLO, WORLD!  "
print(s.replace("World", "Python"))  # "  Hello, Python!  "
print(s.split(","))     # ['  Hello', ' World!  ']
print(" ".join(["a", "b", "c"]))  # "a b c"

# String checks
print("hello".startswith("he"))  # True
print("hello".endswith("lo"))    # True
print("123".isdigit())           # True
print("abc".isalpha())           # True
print("hello".find("ll"))        # 2 (index of substring)

7 Input & Output

Python uses print() for output and input() for user input.

Python - I/O
# Basic print
print("Hello, World!")

# Print with multiple arguments
print("Name:", "Alice", "Age:", 25)
# Output: Name: Alice Age: 25

# Print with custom separator and end
print("A", "B", "C", sep="-")      # A-B-C
print("Hello", end=" ")             # No newline
print("World")                       # Hello World

# User input (always returns string)
name = input("Enter your name: ")
print(f"Hello, {name}!")

# Input with type conversion
age = int(input("Enter your age: "))
price = float(input("Enter price: "))

# Simple calculator example
num1 = float(input("First number: "))
num2 = float(input("Second number: "))
print(f"Sum: {num1 + num2}")
print(f"Product: {num1 * num2}")
▶ INTERACTIVE OUTPUT
Enter your name: Alice Hello, Alice! First number: 5 Second number: 3 Sum: 8.0 Product: 15.0

8 If-Else Statements

Python uses if, elif, and else for conditional execution. Indentation defines code blocks.

Python - Conditionals
# Basic if-else
age = 18

if age >= 18:
    print("You are an adult")
else:
    print("You are a minor")

# if-elif-else chain
score = 85

if score >= 90:
    grade = "A"
elif score >= 80:
    grade = "B"
elif score >= 70:
    grade = "C"
elif score >= 60:
    grade = "D"
else:
    grade = "F"

print(f"Grade: {grade}")  # Grade: B

# Nested conditions
num = 15

if num > 0:
    if num % 2 == 0:
        print("Positive and even")
    else:
        print("Positive and odd")
else:
    print("Not positive")

# Ternary operator (one-liner)
status = "Adult" if age >= 18 else "Minor"
print(status)

# Multiple conditions
x = 5
if 0 < x < 10:           # Python allows chaining
    print("x is between 0 and 10")

# Truthy and Falsy values
# Falsy: None, 0, "", [], {}, set(), False
# Everything else is truthy

name = ""
if name:
    print(f"Hello, {name}")
else:
    print("Name is empty")

⚠️ Indentation Matters!

Python uses indentation (4 spaces recommended) to define code blocks. Incorrect indentation causes IndentationError. Always be consistent!

9 Loops

Python provides for and while loops for iteration.

Python - Loops
# For loop with range
for i in range(5):
    print(i)  # 0, 1, 2, 3, 4

# range(start, stop, step)
for i in range(1, 10, 2):
    print(i)  # 1, 3, 5, 7, 9

# For loop with list
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
    print(fruit)

# enumerate() - get index and value
for index, fruit in enumerate(fruits):
    print(f"{index}: {fruit}")
# 0: apple, 1: banana, 2: cherry

# Loop through string
for char in "Python":
    print(char, end=" ")  # P y t h o n

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

# break - exit loop
for i in range(10):
    if i == 5:
        break
    print(i)  # 0, 1, 2, 3, 4

# continue - skip iteration
for i in range(5):
    if i == 2:
        continue
    print(i)  # 0, 1, 3, 4 (skips 2)

# else clause (runs if loop completes normally)
for i in range(3):
    print(i)
else:
    print("Loop finished!")

10 Comprehensions

Comprehensions are a concise way to create lists, dictionaries, and sets in Python.

Python - Comprehensions
# List comprehension
# Traditional way
squares = []
for x in range(5):
    squares.append(x ** 2)

# Comprehension way
squares = [x ** 2 for x in range(5)]
print(squares)  # [0, 1, 4, 9, 16]

# With condition
evens = [x for x in range(10) if x % 2 == 0]
print(evens)  # [0, 2, 4, 6, 8]

# If-else in comprehension
labels = ["even" if x % 2 == 0 else "odd" for x in range(5)]
print(labels)  # ['even', 'odd', 'even', 'odd', 'even']

# Nested comprehension
matrix = [[j for j in range(3)] for i in range(3)]
print(matrix)  # [[0,1,2], [0,1,2], [0,1,2]]

# Dictionary comprehension
squares_dict = {x: x**2 for x in range(5)}
print(squares_dict)  # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# Set comprehension
unique_lengths = {len(word) for word in ["hello", "world", "hi"]}
print(unique_lengths)  # {2, 5}

# Generator expression (memory efficient)
gen = (x ** 2 for x in range(1000000))
print(next(gen))  # 0
print(next(gen))  # 1

💡 When to Use Comprehensions

Use comprehensions for simple transformations and filtering. For complex logic with multiple statements, stick to regular loops for better readability.

11 Lists

Lists are mutable, ordered collections that can hold items of different types.

Python - Lists
# Creating lists
numbers = [1, 2, 3, 4, 5]
mixed = [1, "hello", 3.14, True]
empty = []
from_range = list(range(5))  # [0, 1, 2, 3, 4]

# Accessing elements
print(numbers[0])    # 1 (first)
print(numbers[-1])   # 5 (last)
print(numbers[1:4])  # [2, 3, 4] (slice)

# Modifying lists
numbers[0] = 10           # [10, 2, 3, 4, 5]
numbers.append(6)          # Add to end
numbers.insert(0, 0)       # Insert at index
numbers.extend([7, 8])     # Add multiple

# Removing elements
numbers.remove(3)          # Remove by value
popped = numbers.pop()     # Remove & return last
popped = numbers.pop(0)    # Remove & return at index
del numbers[0]             # Delete by index
numbers.clear()            # Remove all

# List methods
nums = [3, 1, 4, 1, 5, 9, 2, 6]
print(len(nums))           # 8
print(nums.count(1))       # 2
print(nums.index(5))       # 4
print(min(nums))           # 1
print(max(nums))           # 9
print(sum(nums))           # 31

# Sorting
nums.sort()                # In-place sort
nums.sort(reverse=True)   # Descending
sorted_nums = sorted(nums) # Returns new list
nums.reverse()             # Reverse in-place

# Copying lists
original = [1, 2, 3]
copy1 = original.copy()    # Shallow copy
copy2 = list(original)     # Shallow copy
copy3 = original[:]        # Shallow copy

# List unpacking
a, b, c = [1, 2, 3]
first, *rest = [1, 2, 3, 4]  # first=1, rest=[2,3,4]

12 Tuples

Tuples are immutable sequences. Once created, they cannot be modified.

Python - Tuples
# Creating tuples
point = (10, 20)
single = (42,)          # Note the comma!
from_list = tuple([1, 2, 3])
without_parens = 1, 2, 3  # Also a tuple

# Accessing elements
print(point[0])    # 10
print(point[-1])   # 20
print(point[0:1])  # (10,)

# Tuples are immutable!
# point[0] = 5  # TypeError!

# Tuple unpacking
x, y = point
print(f"x={x}, y={y}")  # x=10, y=20

# Swap variables
a, b = 1, 2
a, b = b, a
print(a, b)  # 2, 1

# Tuple methods
nums = (1, 2, 2, 3)
print(nums.count(2))  # 2
print(nums.index(3))  # 3

# Named tuples (more readable)
from collections import namedtuple

Point = namedtuple('Point', ['x', 'y'])
p = Point(10, 20)
print(p.x, p.y)  # 10, 20

# Why use tuples?
# - Faster than lists
# - Can be used as dictionary keys
# - Protect data from modification
# - Indicate "this data shouldn't change"

13 Dictionaries

Dictionaries store key-value pairs. They're unordered (Python 3.7+ maintains insertion order), mutable, and extremely fast for lookups.

Python - Dictionaries
# Creating dictionaries
person = {
    "name": "Alice",
    "age": 25,
    "city": "NYC"
}
empty = {}
from_pairs = dict([("a", 1), ("b", 2)])

# Accessing values
print(person["name"])       # "Alice"
print(person.get("name"))   # "Alice"
print(person.get("job", "N/A"))  # "N/A" (default)

# Modifying dictionaries
person["age"] = 26            # Update value
person["email"] = "a@b.com"  # Add new key
person.update({"job": "Dev"}) # Update multiple

# Removing elements
del person["city"]           # Delete key
age = person.pop("age")       # Remove & return
person.clear()                # Remove all

# Dictionary methods
user = {"name": "Bob", "age": 30}
print(user.keys())    # dict_keys(['name', 'age'])
print(user.values())  # dict_values(['Bob', 30])
print(user.items())   # dict_items([('name','Bob'),...])

# Looping through dictionaries
for key in user:
    print(key, user[key])

for key, value in user.items():
    print(f"{key}: {value}")

# Check if key exists
if "name" in user:
    print("Name exists")

# Nested dictionaries
users = {
    "user1": {"name": "Alice", "age": 25},
    "user2": {"name": "Bob", "age": 30}
}
print(users["user1"]["name"])  # "Alice"

14 Sets

Sets are unordered collections of unique elements. They're perfect for removing duplicates and mathematical set operations.

Python - Sets
# Creating sets
fruits = {"apple", "banana", "cherry"}
from_list = set([1, 2, 2, 3])  # {1, 2, 3}
empty = set()  # Note: {} creates empty dict!

# Remove duplicates from list
nums = [1, 2, 2, 3, 3, 3]
unique = list(set(nums))  # [1, 2, 3]

# Adding elements
fruits.add("orange")
fruits.update(["mango", "grape"])

# Removing elements
fruits.remove("banana")   # Raises error if not found
fruits.discard("kiwi")    # No error if not found
fruits.pop()               # Remove arbitrary element

# Set operations
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

print(a | b)    # Union: {1, 2, 3, 4, 5, 6}
print(a & b)    # Intersection: {3, 4}
print(a - b)    # Difference: {1, 2}
print(a ^ b)    # Symmetric diff: {1, 2, 5, 6}

# Or use methods
print(a.union(b))
print(a.intersection(b))
print(a.difference(b))

# Set comparisons
print({1, 2}.issubset({1, 2, 3}))     # True
print({1, 2, 3}.issuperset({1, 2}))  # True
print({1, 2}.isdisjoint({3, 4}))     # True (no common)

# Frozen set (immutable)
immutable = frozenset([1, 2, 3])
# immutable.add(4)  # Error!

15 Functions

Functions are reusable blocks of code defined with the def keyword.

Python - Functions
# Basic function
def greet():
    print("Hello, World!")

greet()  # Call the function

# Function with parameters
def greet_person(name):
    print(f"Hello, {name}!")

greet_person("Alice")

# Function with return value
def add(a, b):
    return a + b

result = add(5, 3)
print(result)  # 8

# Default parameters
def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"

print(greet("Alice"))           # Hello, Alice!
print(greet("Bob", "Hi"))       # Hi, Bob!

# Keyword arguments
print(greet(greeting="Hey", name="Charlie"))

# *args - variable positional arguments
def sum_all(*args):
    return sum(args)

print(sum_all(1, 2, 3, 4))  # 10

# **kwargs - variable keyword arguments
def print_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_info(name="Alice", age=25, city="NYC")

# Multiple return values (tuple)
def min_max(numbers):
    return min(numbers), max(numbers)

minimum, maximum = min_max([3, 1, 4, 1, 5])
print(f"Min: {minimum}, Max: {maximum}")

# Docstring
def calculate_area(radius):
    """
    Calculate the area of a circle.
    
    Args:
        radius: The radius of the circle
    
    Returns:
        The area of the circle
    """
    return 3.14159 * radius ** 2

16 Lambda Functions

Lambda functions are small, anonymous functions defined with the lambda keyword.

Python - Lambda
# Basic lambda
square = lambda x: x ** 2
print(square(5))  # 25

# Multiple parameters
add = lambda a, b: a + b
print(add(3, 4))  # 7

# Lambda with built-in functions
numbers = [3, 1, 4, 1, 5, 9, 2]

# sorted with key
words = ["banana", "apple", "cherry"]
sorted_by_len = sorted(words, key=lambda x: len(x))
print(sorted_by_len)  # ['apple', 'banana', 'cherry']

# map - apply function to all items
squared = list(map(lambda x: x**2, numbers))
print(squared)  # [9, 1, 16, 1, 25, 81, 4]

# filter - keep items that match condition
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens)  # [4, 2]

# reduce - accumulate values
from functools import reduce
product = reduce(lambda x, y: x * y, [1, 2, 3, 4])
print(product)  # 24

# Sorting objects by attribute
users = [
    {"name": "Alice", "age": 25},
    {"name": "Bob", "age": 30},
    {"name": "Charlie", "age": 20}
]
by_age = sorted(users, key=lambda u: u["age"])
print(by_age)
# [{'name': 'Charlie', 'age': 20}, {'name': 'Alice', 'age': 25}, {'name': 'Bob', 'age': 30}]

17 Classes & Objects

Python is an object-oriented language. Classes are blueprints for creating objects.

Python - Classes
# Basic class
class Person:
    # Constructor
    def __init__(self, name, age):
        self.name = name    # Instance attribute
        self.age = age
    
    # Instance method
    def greet(self):
        return f"Hello, I'm {self.name}"
    
    # String representation
    def __str__(self):
        return f"Person({self.name}, {self.age})"

# Create objects
alice = Person("Alice", 25)
bob = Person("Bob", 30)

print(alice.name)      # Alice
print(alice.greet())   # Hello, I'm Alice
print(alice)           # Person(Alice, 25)

# Class with class attribute
class Dog:
    species = "Canis familiaris"  # Class attribute (shared)
    
    def __init__(self, name, breed):
        self.name = name      # Instance attribute
        self.breed = breed
    
    def bark(self):
        return f"{self.name} says Woof!"

buddy = Dog("Buddy", "Golden Retriever")
print(buddy.species)  # Canis familiaris
print(buddy.bark())   # Buddy says Woof!

# Class with private attributes
class BankAccount:
    def __init__(self, balance):
        self._balance = balance    # Convention: "private"
        self.__secret = 123        # Name mangling
    
    def deposit(self, amount):
        self._balance += amount
    
    def get_balance(self):
        return self._balance

# Property decorator
class Circle:
    def __init__(self, radius):
        self._radius = radius
    
    @property
    def radius(self):
        return self._radius
    
    @radius.setter
    def radius(self, value):
        if value > 0:
            self._radius = value
    
    @property
    def area(self):
        return 3.14159 * self._radius ** 2

c = Circle(5)
print(c.area)    # 78.53975
c.radius = 10
print(c.area)    # 314.159

18 Inheritance

Inheritance allows a class to inherit attributes and methods from another class.

Python - Inheritance
# Base class
class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        raise NotImplementedError("Subclass must implement")
    
    def info(self):
        return f"I am {self.name}"

# Child classes
class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)  # Call parent constructor
        self.breed = breed
    
    def speak(self):
        return f"{self.name} says Woof!"

class Cat(Animal):
    def speak(self):
        return f"{self.name} says Meow!"

# Using inheritance
dog = Dog("Buddy", "Labrador")
cat = Cat("Whiskers")

print(dog.speak())  # Buddy says Woof!
print(cat.speak())  # Whiskers says Meow!
print(dog.info())   # I am Buddy (inherited)
print(dog.breed)   # Labrador

# Check inheritance
print(isinstance(dog, Dog))     # True
print(isinstance(dog, Animal))  # True
print(issubclass(Dog, Animal))  # True

# Multiple inheritance
class Flyable:
    def fly(self):
        return "Flying!"

class Swimmable:
    def swim(self):
        return "Swimming!"

class Duck(Animal, Flyable, Swimmable):
    def speak(self):
        return f"{self.name} says Quack!"

duck = Duck("Donald")
print(duck.speak())  # Donald says Quack!
print(duck.fly())    # Flying!
print(duck.swim())   # Swimming!

19 File Handling

Python makes it easy to read from and write to files using the built-in open() function.

Python - Files
# Writing to a file
with open("example.txt", "w") as file:
    file.write("Hello, World!\n")
    file.write("This is line 2.\n")
# File automatically closed after 'with' block

# Reading entire file
with open("example.txt", "r") as file:
    content = file.read()
    print(content)

# Reading line by line
with open("example.txt", "r") as file:
    for line in file:
        print(line.strip())  # Remove trailing newline

# Reading into list
with open("example.txt", "r") as file:
    lines = file.readlines()

# Appending to file
with open("example.txt", "a") as file:
    file.write("Appended line.\n")

# File modes:
# "r"  - Read (default)
# "w"  - Write (overwrites)
# "a"  - Append
# "x"  - Create (fails if exists)
# "b"  - Binary mode (rb, wb)
# "+"  - Read and write (r+, w+)

# Working with JSON
import json

data = {"name": "Alice", "age": 25}

# Write JSON
with open("data.json", "w") as file:
    json.dump(data, file, indent=2)

# Read JSON
with open("data.json", "r") as file:
    loaded = json.load(file)
    print(loaded["name"])  # Alice

# Working with CSV
import csv

# Write CSV
with open("users.csv", "w", newline="") as file:
    writer = csv.writer(file)
    writer.writerow(["Name", "Age"])
    writer.writerow(["Alice", 25])
    writer.writerow(["Bob", 30])

# Read CSV
with open("users.csv", "r") as file:
    reader = csv.reader(file)
    for row in reader:
        print(row)

💡 Always Use with Statement

The with statement automatically closes the file when done, even if an exception occurs. This prevents resource leaks and is the recommended way to handle files.

20 Exception Handling

Exceptions are errors that occur during program execution. Python provides try-except blocks to handle them gracefully.

Python - Exceptions
# Basic try-except
try:
    result = 10 / 0
except ZeroDivisionError:
    print("Cannot divide by zero!")

# Multiple exception types
try:
    num = int("abc")
except ValueError:
    print("Invalid number format")
except TypeError:
    print("Type error occurred")

# Catch multiple in one except
try:
    # risky code
    pass
except (ValueError, TypeError) as e:
    print(f"Error: {e}")

# else and finally
try:
    result = 10 / 2
except ZeroDivisionError:
    print("Division error")
else:
    print(f"Result: {result}")  # Runs if no exception
finally:
    print("Always runs")  # Cleanup code

# Raising exceptions
def validate_age(age):
    if age < 0:
        raise ValueError("Age cannot be negative")
    if age > 150:
        raise ValueError("Age too high")
    return True

try:
    validate_age(-5)
except ValueError as e:
    print(e)  # Age cannot be negative

# Custom exceptions
class InsufficientFundsError(Exception):
    def __init__(self, balance, amount):
        self.message = f"Cannot withdraw {amount}. Balance: {balance}"
        super().__init__(self.message)

def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientFundsError(balance, amount)
    return balance - amount

try:
    withdraw(100, 150)
except InsufficientFundsError as e:
    print(e)  # Cannot withdraw 150. Balance: 100

21 Modules & Packages

Modules are Python files that can be imported. Packages are directories containing modules.

Python - Modules
# Importing modules
import math
print(math.pi)           # 3.141592...
print(math.sqrt(16))     # 4.0

# Import with alias
import numpy as np
import pandas as pd

# Import specific items
from math import pi, sqrt
print(pi)                # 3.141592... (no math. prefix)

# Import all (avoid in production)
from math import *

# Common built-in modules
import os
print(os.getcwd())       # Current directory
print(os.listdir("."))   # List directory contents

import datetime
now = datetime.datetime.now()
print(now.strftime("%Y-%m-%d %H:%M:%S"))

import random
print(random.randint(1, 100))   # Random int 1-100
print(random.choice(["a", "b", "c"]))  # Random choice

# Creating your own module
# mymodule.py:
# def greet(name):
#     return f"Hello, {name}!"
# 
# PI = 3.14159

# main.py:
# import mymodule
# print(mymodule.greet("Alice"))
# print(mymodule.PI)

# Package structure:
# mypackage/
#     __init__.py
#     module1.py
#     module2.py
#     subpackage/
#         __init__.py
#         module3.py

# from mypackage import module1
# from mypackage.subpackage import module3

22 Decorators

Decorators are a powerful feature that allows you to modify the behavior of functions or classes.

Python - Decorators
# Basic decorator
def my_decorator(func):
    def wrapper():
        print("Before function call")
        func()
        print("After function call")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()
# Output:
# Before function call
# Hello!
# After function call

# Decorator with arguments
def decorator_with_args(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} returned {result}")
        return result
    return wrapper

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

add(3, 5)
# Calling add
# add returned 8

# Timer decorator (practical example)
import time

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

@timer
def slow_function():
    time.sleep(1)
    return "Done"

slow_function()  # slow_function took 1.0012s

# Built-in decorators
class MyClass:
    @staticmethod
    def static_method():
        return "No self needed"
    
    @classmethod
    def class_method(cls):
        return f"Called on {cls.__name__}"
    
    @property
    def my_property(self):
        return "Property value"

💡 Common Use Cases for Decorators

• Logging and debugging
• Performance timing
• Authentication and authorization
• Caching/memoization
• Input validation