🔹 Variables in Python

A variable stores a value. Python is dynamically typed, so you don't need to declare a type.

# Basic variable assignment
age = 25
name = "Alice"
price = 99.99
is_python_fun = True

Naming Rules

  • Must start with letter or underscore
  • Can contain letters, numbers, and underscores
  • Case-sensitive (Age ≠ AGE ≠ age)
  • Use snake_case convention (my_variable)

Dynamic Typing

# Reassign different types
value = 100         # Integer
value = "Python"    # Now string
value = [1, 2, 3]   # Now list

Type Conversion

num_str = str(42)    # "42"
str_num = int("123")  # 123
num_float = float(7) # 7.0

Common Methods

# String methods
name = "python"
upper_name = name.upper()  # "PYTHON"

# List methods
numbers = [1, 2, 3]
numbers.append(4)  # [1, 2, 3, 4]

# Dictionary methods
user = {"name": "Alice", "age": 30}
keys = user.keys()  # dict_keys(['name', 'age'])

Multiple Assignment

# Assign multiple variables
x, y, z = 1, 2, 3

# Swap values
a, b = 10, 20
a, b = b, a  # a=20, b=10

Formatted Strings (f-strings)

# Common formatting with f-strings
name = "Bob"
age = 25
print(f"{name} is {age} years old")  # Bob is 25 years old

# Centering a string
name = "Bob"
print(f"{name:{'-'}^20}")  # --------Bob---------

# Padding string to the right
name = "Bob"
print(f"{name:{'-'}<10}")  # Bob-------

# Padding string to the left
name = "Bob"
print(f"{name:{'-'}>10}")  # -------Bob

🔹 Data Types in Python

Python uses dynamic typing and has several built-in data types:

Type Description Example
int Integer numbers 42
float Floating-point numbers 3.1415
str Text sequences "Hello"
bool Boolean values True/False
list Mutable sequence [1, 2, 3]
tuple Immutable sequence (1, 2, 3)
dict Key-value pairs {"key": "value"}
set Unique elements {1, 2, 3}

Type Conversion

int("25")        # 25 → Convert to integer
float(7)         # 7.0 → Convert to float
str(100)         # "100" → Convert to string
list("hello")    # ['h', 'e', 'l', 'l', 'o']
tuple([1,2,3])   # (1, 2, 3)
set([1,2,2,3])   # {1, 2, 3}

Type Checking

type(42)         # → <class 'int'>
isinstance(3.14, float)  # → True

Special Values

None         # Represents absence of value
True/False   # Boolean constants
...

🔹 Type Checking & Conversion

Type Checking

num = 42
print(type(num))          # <class 'int'>

name = "Alice"
print(isinstance(name, str))  # True

mixed_list = [1, "two", 3.0]
print(type(mixed_list))   # <class 'list'>

Conversion Functions

# String to numeric
int_num = int("100")        # 100
float_num = float("3.14")   # 3.14

# Numeric to string
str_num = str(255)          # "255"

# List conversions
num_tuple = tuple([1, 2, 3])  # (1, 2, 3)
char_list = list("Hello")    # ['H', 'e', 'l', 'l', 'o']

# Boolean conversion
bool_val = bool(1)          # True
int_from_bool = int(True)   # 1

Special Conversions

# Base conversion
hex_num = int("ff", 16)     # 255
bin_num = bin(42)           # '0b101010'

# ASCII conversion
char = chr(65)              # 'A'
code = ord('A')             # 65

# String formatting
formatted = f"{255:x}"      # "ff" (hexadecimal)

Error Handling

try:
    num = int("123a")
except ValueError as e:
    print(f"Conversion error: {e}")

# Check before conversion
value = "3.14"
if value.replace('.', '', 1).isdigit():
    print(float(value))

🔹 Operators & Expressions

Operators in Python allow you to perform calculations and logical comparisons.

🔸 Arithmetic Operators

print(10 + 3)   # Addition → 13
print(10 - 3)   # Subtraction → 7
print(10 * 3)   # Multiplication → 30
print(10 ** 3)  # Exponentiation → 1000
print(10 / 3)   # Division → 3.333...
print(10 // 3)  # Floor Division → 3
print(10 % 3)   # Modulus → 1

🔸 Assignment Operators

x = 5
x += 3   # x = x + 3 → 8
x -= 2   # x = x - 2 → 6
x *= 4   # x = x * 4 → 24
x /= 3   # x = x / 3 → 8.0
x **= 2  # x = x ** 2 → 64.0
x %= 5   # x = x % 5 → 4.0

🔸 Comparison Operators

print(5 == 5)    # Equal → True
print(5 != 3)    # Not Equal → True
print(5 > 3)     # Greater Than → True
print(5 <= 3)    # Less or Equal → False
print("a" == "A")# Case-sensitive → False
print(1.0 == 1)  # Value comparison → True

🔸 Logical Operators

age = 25
has_license = True

# And/Or combinations
can_drive = age >= 18 and has_license
discount = age <= 12 or age >= 65

# Truthy/Falsy evaluation
name = ""
if not name:
    print("Name is required")

🔸 Identity Operators

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

x = None
print(x is None)  # True

🔸 Membership Operators

colors = ["red", "green", "blue"]
print("green" in colors)    # True
print("yellow" not in colors) # True

text = "Hello World"
print("World" in text)      # True

🔸 Bitwise Operators

a = 60    # 0011 1100
b = 13    # 0000 1101

print(a & b)   # AND → 12 (0000 1100)
print(a | b)   # OR → 61 (0011 1101)
print(a ^ b)   # XOR → 49 (0011 0001)
print(~a)      # NOT → -61
print(a << 2)  # Shift left → 240
print(a >> 2)  # Shift right → 15

🔸 Walrus Operator

# Assignment expression (Python 3.8+)
if (n := len(items)) > 10:
    print(f"List is long ({n} items)")

# In list comprehension
[x for x in range(100) if (square := x**2) < 100]

# In while loop
while (chunk := file.read(8192)):

🔸 Order of Operations

From highest to lowest precedence:

  1. () - Parentheses
  2. ** - Exponentiation
  3. ~ + - - Unary operators
  4. * / % // - Multiplication/Division
  5. + - - Addition/Subtraction
  6. << >> - Bitwise shifts
  7. & - Bitwise AND
  8. ^ | - Bitwise XOR/OR
  9. := - Assignment expression (Walrus)
  10. <= < > >= == != - Comparisons
  11. is, is not, in, not in - Identity/Membership
  12. not - Logical NOT
  13. and - Logical AND
  14. or - Logical OR

🔹 Conditional Statements

Python supports if, elif, and else statements for decision-making.

🔸 Basic Syntax

x = 20
if x > 10:
    print("Greater than 10")
elif x == 10:
    print("Exactly 10")
else:
    print("Less than 10")

🔸 Multiple Conditions

age = 25
has_license = True

if age >= 18 and has_license:
    print("Can drive")
elif age >= 16 or has_license:
    print("Learner's permit")
else:
    print("Cannot drive")

🔸 Nested Conditionals

num = 15
if num > 0:
    if num % 2 == 0:
        print("Positive even number")
    else:
        print("Positive odd number")
else:
    print("Non-positive number")

🔸 Ternary Operator

# Short-hand if-else
age = 20
status = "Adult" if age >= 18 else "Minor"
print(status)  # Adult

🔹 Loops in Python

Loops help automate repetitive tasks.

🔸 For Loop

# Iterate through list
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
    print(fruit.upper())

# With index using enumerate
for index, fruit in enumerate(fruits):
    print(f"{index}: {fruit}")

# Range with step
for num in range(0, 10, 2):
    print(num)  # 0, 2, 4, 6, 8

🔸 While Loop

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

# With break/continue
while True:
    user_input = input("Enter 'q' to quit: ")
    if user_input == 'q':
        break
    elif user_input.isdigit():
        continue
    print(f"You entered: {user_input}")

🔸 Loop Control

# Else clause in loops
for n in range(2, 10):
    for x in range(2, n):
        if n % x == 0:
            break
    else:
        print(f"{n} is prime")

# Zip multiple iterables
names = ["Alice", "Bob"]
ages = [25, 30]
for name, age in zip(names, ages):
    print(f"{name} is {age} years old")

🔸 Comprehensions

# List comprehension
squares = [x**2 for x in range(10)]

# Dictionary comprehension
square_dict = {x: x**2 for x in range(5)}

# Set comprehension
unique_lengths = {len(word) for word in ["hello", "world", "python"]}

🔹 Functions in Python

Functions help organize code into reusable blocks.

🔸 Basic Function

def greet(name):
    return "Hello " + name

print(greet("Alice"))  # Outputs: Hello Alice

🔸 Parameters & Return Values

# Multiple parameters and return values
def calculate(x, y):
    sum = x + y
    product = x * y
    return sum, product

s, p = calculate(3, 4)
print(f"Sum: {s}, Product: {p}")  # Sum: 7, Product: 12

🔸 Advanced Parameters

# *args for variable arguments
def average(*numbers):
    return sum(numbers) / len(numbers)

print(average(5, 10, 15))  # 10.0

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

user_profile(name="Alice", age=25, city="NY")

🔸 Special Function Types

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

# Decorator functions
def logger(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@logger
def example():
    print("Hello from example")

example()

🔸 Recursion

def factorial(n):
    return 1 if n <= 1 else n * factorial(n-1)

print(factorial(5))  # 120

🔸 Function Best Practices

# Type hints and docstrings
def power(base: float, exponent: int) -> float:
    """Calculate base raised to the power of exponent.
    
    Args:
        base: The base number
        exponent: The exponent (must be positive integer)
    
    Returns:
        The result of base^exponent
    """
    return base ** exponent

print(power(2, 3))  # 8.0

🔸 Scope & Closures

def outer():
    message = "Hello"
    def inner():
        print(message)  # Accessing outer scope
    return inner

closure = outer()
closure()  # Hello

🔸 Global & Local Variables

count = 0  # Global variable

def increment():
    global count  # Declare global variable
    local_num = 5  # Local variable
    count += local_num

increment()
print(count)  # 5

# Accessing global without modification
def show_global():
    print("Global count:", count)

show_global()  # Global count: 5

Note: Avoid using global variables when possible. Use function parameters and return values instead.

🔹 Lists & Tuples in Python

🔸 Lists

Ordered, mutable collection. Created with square brackets []

# Basic operations
nums = [1, 2, 3, 4, 5]
print(nums[1])        # 2 (indexing)
print(nums[-1])       # 5 (negative indexing)
print(nums[1:4])      # [2, 3, 4] (slicing)
print(len(nums))      # 5 (length)
print(3 in nums)      # True (membership check)
# Common methods
fruits = ["apple", "banana"]
fruits.append("orange")      # Add item
fruits.insert(1, "mango")    # Insert at position
fruits.remove("banana")      # Remove item
popped = fruits.pop(0)       # Remove and return item
fruits.sort()                # Sort in-place
fruits.reverse()             # Reverse in-place
print(fruits)                # ['orange', 'mango']
# List operations
a = [1, 2] + [3, 4]      # Concatenation → [1,2,3,4]
b = [0] * 3              # Repetition → [0,0,0]
c = [x**2 for x in a]    # List comprehension → [1,4,9,16]
d = a.copy()             # Shallow copy

🔸 Tuples

Ordered, immutable collection. Created with parentheses ()

# Basic operations
colors = ('red', 'green', 'blue')
print(colors[1])        # green
print(colors[::2])      # ('red', 'blue') (slicing)
print(len(colors))      # 3
print('red' in colors)  # True
# Tuple methods
numbers = (1, 2, 2, 3)
print(numbers.count(2))  # 2 (occurrences)
print(numbers.index(3)) # 3 (first occurrence index)
# Tuple unpacking
point = (10, 20)
x, y = point            # x=10, y=20

# Returning multiple values
def min_max(nums):
    return min(nums), max(nums)
result = min_max([5,2,8,1])  # (1,8)
# Immutability note
# colors[0] = 'yellow'  # TypeError
# Can contain mutable elements
mixed = (1, [2, 3], "hello")
mixed[1].append(4)     # Valid → (1, [2,3,4], "hello")

🔸 List vs Tuple Comparison

Feature List Tuple
Mutability Mutable Immutable
Memory More memory Less memory
Use Case Changing data Fixed data
Methods append(), sort(), etc count(), index()

🔹 Dictionaries & Sets

🔸 Dictionaries

Mutable mapping type storing key-value pairs. Created with {} or dict()

# Dictionary creation
empty_dict = {}
grades = dict(Alice=95, Bob=88)
person = {"name": "Alice", "age": 25, "scores": [95, 88]}

# Access methods
print(person.get("name"))          # Alice
print(person.get("height", 160))    # 160 (default if key missing)
print(person.keys())                # dict_keys(['name', 'age', 'scores'])
print(person.values())              # dict_values(['Alice', 25, [95, 88]])
print(person.items())               # dict_items([('name', 'Alice'), ...])
# Dictionary operations
person["city"] = "New York"         # Add/update
del person["age"]                   # Delete key
popped = person.pop("scores")       # Remove and return value
person.update({"age": 30, "job": "Engineer"})  # Merge dictionaries

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

🔸 Sets

Unordered collection of unique elements. Created with {} or set()

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

print(a | b)   # Union → {1,2,3,4}
print(a & b)   # Intersection → {2,3}
print(a - b)   # Difference → {1}
print(a ^ b)   # Symmetric Difference → {1,4}
# Set methods
s = {1, 2, 3}
s.add(4)                # {1,2,3,4}
s.discard(2)            # {1,3,4} (no error if missing)
s.remove(3)             # {1,4} (raises KeyError if missing)
s.update([5,6])         # {1,4,5,6}
popped = s.pop()        # Remove and return arbitrary element

# Set comprehension
even_squares = {x**2 for x in range(10) if x%2 == 0}

🔸 Dictionary vs Set Comparison

Feature Dictionary Set
Elements Key-value pairs Unique values
Ordering Insertion ordered (Python 3.7+) Unordered
Mutability Mutable Mutable
Use Case Data records, lookups Membership testing, duplicates removal

🔹 Object-Oriented Programming (OOP)

🔸 Encapsulation & Properties

class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius  # Protected attribute
    
    @property
    def fahrenheit(self):
        return (self._celsius * 9/5) + 32
    
    @fahrenheit.setter
    def fahrenheit(self, value):
        self._celsius = (value - 32) * 5/9

temp = Temperature(25)
print(temp.fahrenheit)  # 77.0
temp.fahrenheit = 32
print(temp._celsius)     # 0.0

🔸 Multiple Inheritance

class Flyer:
    def fly(self):
        return "Flying"

class Swimmer:
    def swim(self):
        return "Swimming"

class Duck(Flyer, Swimmer):
    pass

duck = Duck()
print(duck.fly())   # Flying
print(duck.swim())  # Swimming

🔸 Abstract Base Classes

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
    
    def area(self):
        return 3.14 * self.radius ** 2

# shape = Shape()  # Error
circle = Circle(5)
print(circle.area())  # 78.5

🔸 Operator Overloading

class Book:
    def __init__(self, title, pages):
        self.title = title
        self.pages = pages
    
    def __add__(self, other):
        return Book(f"{self.title} & {other.title}", self.pages + other.pages)
    
    def __str__(self):
        return f"'{self.title}' ({self.pages} pages)"

b1 = Book("Python 101", 300)
b2 = Book("OOP Guide", 200)
combined = b1 + b2
print(combined)  # 'Python 101 & OOP Guide' (500 pages)

🔸 Decorators (Logging/Caching)

import functools
import time

# Logging decorator
def logger(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}")
        result = func(*args, **kwargs)
        print(f"Finished {func.__name__}")
        return result
    return wrapper

# Caching decorator
def cache(max_size=128):
    def decorator(func):
        cache = {}
        @functools.wraps(func)
        def wrapper(*args):
            if args in cache:
                return cache[args]
            result = func(*args)
            cache[args] = result
            return result
        return wrapper
    return decorator

class Calculator:
    @logger
    def add(self, a, b):
        return a + b
    
    @cache()
    def factorial(self, n):
        return 1 if n <= 1 else n * self.factorial(n-1)

calc = Calculator()
print(calc.add(2, 3))  # Logs calls
print(calc.factorial(5))  # Caches results

Common decorator uses: logging, caching, timing, access control, validation

🔹 File Handling in Python

🔸 Basic File Operations

# Modes: r (read), w (write), a (append), r+ (read/write)
# b (binary mode), t (text mode, default)

# Writing to a file
with open("diary.txt", "w") as f:
    f.write("First line\n")
    f.writelines(["Second line\n", "Third line\n"])

# Reading entire content
with open("diary.txt", "r") as f:
    print(f.read())  # Full content

# Reading line by line
with open("diary.txt", "r") as f:
    for line in f:
        print(line.strip())

🔸 File Positioning

with open("data.bin", "wb+") as f:
    # Write binary data
    f.write(b"\x00\x01\x02\x03")
    
    # Move to start (position 0)
    f.seek(0)
    
    # Read first 2 bytes
    print(f.read(2))  # b'\x00\x01'
    
    # Current position
    print(f.tell())   # 2

🔸 CSV Files

import csv

# Writing CSV
with open("data.csv", "w", newline="") as f:
    writer = csv.writer(f)
    writer.writerow(["Name", "Age"])
    writer.writerows([
        ["Alice", 25],
        ["Bob", 30]
    ])

# Reading CSV
with open("data.csv", "r") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(f"{row['Name']}: {row['Age']}")

🔸 JSON Files

import json

# Writing to a JSON file
data = {"name": "Alice", "scores": [88, 95]}
with open("data.json", "w") as f:
    json.dump(data, f, indent=2)

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

🔸 File Management

import os
import shutil

# File operations
os.rename("old.txt", "new.txt")    # Rename
os.remove("file.txt")             # Delete
shutil.copy("src.txt", "dst.txt")  # Copy
shutil.move("src.txt", "dest/")    # Move

# Directory operations
os.mkdir("new_dir")               # Create directory
os.listdir(".")                   # List files
os.path.exists("file.txt")        # Check existence

🔹 Exception Handling

Python uses try/except blocks to handle runtime errors. Built-in exceptions include:

ValueError - Invalid value

TypeError - Invalid operation type

IndexError - Invalid sequence index

KeyError - Missing dictionary key

FileNotFoundError - Missing file

KeyboardInterrupt - User interrupt

🔸 Basic Error Handling

try:
    age = int(input("Enter age: "))
except ValueError:
    print("Please enter a valid number!")

🔸 Advanced Handling

# Catching multiple exceptions
try:
    file = open("data.txt")
    data = file.read()
    value = int(data)
except (FileNotFoundError, ValueError) as e:
    print(f"Error: {str(e)}")
else:
    print("Successfully read value:", value)
finally:
    file.close() if 'file' in locals() else None

🔸 Custom Exceptions

class InvalidEmailError(Exception):
    """Raised when email format is invalid"""
    def __init__(self, email):
        self.email = email
        super().__init__(f"Invalid email: {email}")

def validate_email(email):
    if "@" not in email:
        raise InvalidEmailError(email)
    return True

try:
    validate_email("user.example.com")
except InvalidEmailError as e:
    print(e)

🔸 Exception Chaining

try:
    open("missing.txt")
except FileNotFoundError as e:
    raise RuntimeError("Failed to process file") from e

🔸 Best Practices

✓ Use specific exceptions instead of bare except:

✓ Include cleanup code in finally blocks

✓ Use else for code that should run only if no exceptions

✓ Log exceptions with logging.exception()

🔹 Modules & Imports

Modules allow you to organize code into reusable files. Python's module system includes:

🔸 Import Types

# 1. Standard import
import math
print(math.pi)

# 2. Import with alias
import numpy as np
print(np.array([1,2]))

# 3. From-import
from datetime import datetime
print(datetime.now())

# 4. Wildcard import (not recommended)
from random import *
print(randint(1,10))

🔸 Module Attributes

# mymodule.py
__version__ = "1.0"
def helper():
    return "Help!"

def main():
    print("Running module")

if __name__ == "__main__":
    main()
# main.py
import mymodule
print(mymodule.__version__)  # 1.0
print(mymodule.__name__)     # mymodule

🔸 Packages

# Package structure
my_package/
├── __init__.py
├── utils.py
└── subpackage/
    ├── __init__.py
    └── helpers.py

# Import from package
from my_package.utils import format_data
from my_package.subpackage.helpers import validate

🔸 Common Built-in Modules

sys - System parameters

os - OS operations

math - Math functions

datetime - Date/time handling

json - JSON processing

re - Regular expressions

argparse - CLI parsing

logging - Logging system

🔸 Module Search Path

import sys
print(sys.path)  # List of directories Python searches for modules

# Add custom path
sys.path.append("/path/to/my/modules")

🔸 Reloading Modules

import importlib
import mymodule

# After modifying mymodule.py
importlib.reload(mymodule)

🔹 Lambda Functions

Anonymous functions defined with lambda keyword. Syntax: lambda arguments: expression

🔸 Basic Syntax

# Simple addition
add = lambda a, b: a + b
print(add(3, 5))  # 8

# No arguments
greet = lambda: "Hello World"
print(greet())     # Hello World

🔸 Common Use Cases

# Sorting with key
people = [{"name": "Alice", "age": 25}, {"name": "Bob", "age": 30}]
sorted_ages = sorted(people, key=lambda x: x['age'])
print(sorted_ages)  # [{'name': 'Alice', 'age': 25}, ...]
# Filtering with condition
numbers = [1, 2, 3, 4, 5, 6]
even = list(filter(lambda x: x % 2 == 0, numbers))
print(even)  # [2, 4, 6]
# Reduce with lambda
from functools import reduce
product = reduce(lambda x, y: x * y, [1, 2, 3, 4])
print(product)  # 24

🔸 Advanced Patterns

# Conditional logic
grade = lambda score: 'Pass' if score >= 50 else 'Fail'
print(grade(65))  # Pass

# Multiple arguments
full_name = lambda first, last: f"{first.strip().title()} {last.strip().title()}"
print(full_name("  john ", "doe "))  # John Doe

🔸 Limitations & Best Practices

✓ Use for simple operations only

✓ Avoid complex logic (use regular functions instead)

✓ Can't contain statements (only expressions)

✓ No annotations or docstrings

✓ Preserve readability - don't chain multiple lambdas

🔹 Decorators

Decorators modify or enhance functions without changing their core functionality.

🔸 Basic Decorator

# Basic decorator example
def decorator(func):
    def wrapper():
        print("Before function call")
        func()
        print("After function call")
    return wrapper

@decorator
def hello():
    print("Hello, world!")

hello()
# Decorator with arguments
def repeat(n_times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(n_times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(n_times=3)
def greet(name):
    print(f"Hello {name}")

greet("Alice")  # Prints 3 times
# Class-based decorator
class Timer:
    def __init__(self, func):
        self.func = func
    
    def __call__(self, *args, **kwargs):
        import time
        start = time.time()
        result = self.func(*args, **kwargs)
        end = time.time()
        print(f"Execution time: {end - start:.2f}s")
        return result

@Timer
def long_operation():
    time.sleep(1)

long_operation()  # Prints execution time
# Using functools.wraps to preserve metadata
import functools

def decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper
# Stacking multiple decorators
@decorator1
@decorator2
def my_function():
    pass
# Error handling in decorators
def handle_errors(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            print(f"Error: {str(e)}")
    return wrapper

🔹 Generators in Python

Generators produce values on-the-fly using yield, preserving memory for large datasets.

🔸 Generator Basics

# Generator function example
def fibonacci(limit):
    a, b = 0, 1
    while a < limit:
        yield a
        a, b = b, a + b

# Using generator
for num in fibonacci(100):
    print(num)  # 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89

🔸 Generator Expressions

# Similar to list comprehension but with ()
squares = (x**2 for x in range(10))
print(next(squares))  # 0
print(next(squares))  # 1

# Memory efficient sum
sum_of_squares = sum(x**2 for x in range(1000000))

🔸 Sending Values

def accumulator():
    total = 0
    while True:
        value = yield
        total += value
        yield total

gen = accumulator()
next(gen)  # Prime the generator
print(gen.send(10))  # 10
print(gen.send(20))  # 30

🔸 Yield From

def countdown(n):
    while n > 0:
        yield n
        n -= 1

def countup(n):
    yield from countdown(n)
    yield from range(1, n+1)

for num in countup(3):
    print(num)  # 3, 2, 1, 1, 2, 3

🔸 Generator Methods

def generator_func():
    try:
        while True:
            item = yield
            print(item)
    except GeneratorExit:
        print("Generator closed")

gen = generator_func()
next(gen)  # Start generator
gen.send("Hello")  # Prints Hello
gen.close()         # Prints Generator closed

🔸 Best Practices

✓ Use for memory-efficient iteration

✓ Prefer generator expressions for simple cases

✓ Handle GeneratorExit for cleanup

✓ Use yield from for nested generators

✓ Remember generators are single-use

🔹 Working with CSV & JSON

🔸 Advanced CSV Handling

import csv

# Reading with DictReader
with open("data.csv", "r") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(f"{row['Name']} is {row['Age']} years old")

# Writing with DictWriter
with open("output.csv", "w", newline="") as f:
    fieldnames = ["Name", "Email"]
    writer = csv.DictWriter(f, fieldnames=fieldnames, delimiter=';')
    writer.writeheader()
    writer.writerow({"Name": "Alice", "Email": "alice@example.com"})

# Handling TSV files
with open("data.tsv", "w", newline="") as f:
    writer = csv.writer(f, delimiter='\t')
    writer.writerow(["ID", "Value"])

🔸 Advanced JSON Handling

import json
from datetime import datetime

# Custom JSON encoder
class CustomEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            return obj.isoformat()
        return super().default(obj)

data = {"time": datetime.now(), "name": "Alice"}
json_str = json.dumps(data, cls=CustomEncoder)

# Custom JSON decoder
def datetime_decoder(dct):
    for key, value in dct.items():
        try:
            dct[key] = datetime.fromisoformat(value)
        except:
            pass
    return dct

loaded = json.loads(json_str, object_hook=datetime_decoder)

🔸 Using Pandas

import pandas as pd

# Read CSV
df = pd.read_csv("data.csv", parse_dates=['timestamp'])

# Write JSON
df.to_json("data.json", orient='records')

# Handle large files
chunk_iter = pd.read_csv("large.csv", chunksize=10000)
for chunk in chunk_iter:
    process(chunk)

🔹 Multithreading in Python

🔸 Thread Pools

from concurrent.futures import ThreadPoolExecutor
import time

def task(n):
    time.sleep(n)
    return n * n

with ThreadPoolExecutor(max_workers=3) as executor:
    futures = [executor.submit(task, i) for i in range(1,4)]
    results = [f.result() for f in futures]
print(results)  # [1, 4, 9]

🔸 Thread Synchronization

from threading import Lock

counter = 0
lock = Lock()

def increment():
    global counter
    with lock:
        temp = counter
        temp += 1
        counter = temp

threads = [threading.Thread(target=increment) for _ in range(1000)]
for t in threads:
    t.start()
for t in threads:
    t.join()
print(counter)  # 1000

🔸 Daemon Threads

def background_task():
    while True:
        print("Running...")
        time.sleep(1)

daemon = threading.Thread(target=background_task, daemon=True)
daemon.start()
# Will terminate when main program exits

🔸 Best Practices

✓ Use thread pools instead of manual thread management

✓ Always use locks for shared resources

✓ Prefer ThreadPoolExecutor for most cases

✓ Use daemon threads for background tasks

✓ Avoid the Global Interpreter Lock (GIL) limitations

🔹 Regular Expressions (Regex)

Pattern matching using Python's re module. Common metacharacters:

. Any character

\d Digit (0-9)

\w Word character

\s Whitespace

^ Start of string

$ End of string

[abc] Any of a/b/c

a|b a or b

🔸 Common Methods

import re

# Match object methods
text = "Date: 2023-08-15"
match = re.search(r"(\d{4})-(\d{2})-(\d{2})", text)
if match:
    print(match.group())   # 2023-08-15
    print(match.groups())  # ('2023', '08', '15')
    print(match.start())   # 6
    print(match.end())     # 16

🔸 Substitution

# Replace dates
text = "Copyright 2022. Updated 2023."
new_text = re.sub(r"\d{4}", "YEAR", text)
print(new_text)  # Copyright YEAR. Updated YEAR.

🔸 Compiled Patterns

# Precompile for reuse
phone_re = re.compile(r"\(\d{3}\) \d{3}-\d{4}")
text = "Call (415) 555-1234"
match = phone_re.search(text)
print(match.group())  # (415) 555-1234

🔸 Regex Flags

# Case-insensitive matching
text = "Color or colour?"
matches = re.findall(r"colou?r", text, flags=re.IGNORECASE)
print(matches)  # ['Color', 'colour']

🔸 Advanced Patterns

# Lookaheads and lookbehinds
text = "100kg 200lb"
weights = re.findall(r"\d+(?=kg)", text)  # Positive lookahead
print(weights)  # ['100']

# Named groups
date_re = r"(?P\d{4})-(?P\d{2})"
match = re.search(date_re, "2023-08")
print(match.groupdict())  # {'year': '2023', 'month': '08'}

🔸 Best Practices

✓ Use raw strings (r"pattern")

✓ Precompile patterns for repeated use

✓ Use verbose mode for complex patterns

pattern = re.compile(r"""
    \b             # Word boundary
    [A-Z0-9._%+-]+ # Username
    @ 
    [A-Z0-9.-]+    # Domain
    \. 
    [A-Z]{2,}      # TLD
    \b""", re.VERBOSE | re.IGNORECASE)

✓ Test patterns with online regex testers

🔹 Working with APIs (Requests Module)

Handle HTTP requests with Python's requests library. Common methods:

GET - Retrieve data

POST - Create resource

PUT - Update resource

DELETE - Remove resource

PATCH - Partial update

HEAD - Get headers

🔸 HTTP Methods

# PUT request
response = requests.put("https://api.example.com/items/1", json={"name": "new item"})

# DELETE request
response = requests.delete("https://api.example.com/items/1")

# PATCH request
response = requests.patch("https://api.example.com/items/1", json={"price": 19.99})

🔸 Headers & Authentication

# Custom headers
headers = {
    "User-Agent": "MyApp/1.0",
    "Authorization": "Bearer abc123"
}
response = requests.get("https://api.example.com", headers=headers)

# Basic auth
from requests.auth import HTTPBasicAuth
response = requests.get(
    "https://api.example.com",
    auth=HTTPBasicAuth("user", "pass")
)

🔸 Response Handling

response = requests.get("https://api.example.com")

print(response.status_code)    # 200
print(response.headers)        # Response headers
print(response.text[:100])     # First 100 characters
print(response.json().keys())  # JSON keys
print(response.raise_for_status())  # Raise exception for 4xx/5xx

🔸 Error Handling

try:
    response = requests.get("https://api.example.com", timeout=5)
    response.raise_for_status()
except requests.exceptions.HTTPError as err:
    print(f"HTTP error: {err}")
except requests.exceptions.Timeout:
    print("Request timed out")
except requests.exceptions.RequestException as err:
    print(f"Request error: {err}")

🔸 Using Sessions

with requests.Session() as session:
    session.headers.update({"X-API-Key": "secret"})
    
    # Persistent settings
    response1 = session.get("https://api.example.com/list")
    response2 = session.post("https://api.example.com/create", json={"data": "value"})

🔸 File Uploads

files = {"file": open("report.pdf", "rb")}
response = requests.post("https://api.example.com/upload", files=files)

🔸 Best Practices

✓ Always set timeouts

✓ Use sessions for multiple requests

✓ Handle exceptions properly

✓ Validate SSL certificates

✓ Rate limit your requests

# Safe request with timeout and verification
requests.get("https://api.example.com", timeout=5, verify=True)

🔹 Working with Databases (SQLite3)

🔸 Core Operations

import sqlite3
from contextlib import closing

# Using context manager for connection
with closing(sqlite3.connect("app.db")) as conn:
    with closing(conn.cursor()) as cursor:
        # Parameterized query
        cursor.execute("INSERT INTO users (name, age) VALUES (?, ?)", ("Bob", 30))
        
        # Batch insert
        users = [("Charlie", 25), ("Diana", 28)]
        cursor.executemany("INSERT INTO users (name, age) VALUES (?, ?)", users)
    
    conn.commit()

🔸 Advanced Queries

# Foreign key support
conn.execute("PRAGMA foreign_keys = ON")

# Create table with relations
conn.execute("""CREATE TABLE orders (
    id INTEGER PRIMARY KEY,
    user_id INTEGER,
    amount REAL,
    FOREIGN KEY(user_id) REFERENCES users(id)
)""")

🔸 Transactions

try:
    conn = sqlite3.connect("bank.db")
    conn.execute("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
    conn.execute("UPDATE accounts SET balance = balance + 100 WHERE id = 2")
    conn.commit()
except sqlite3.Error:
    conn.rollback()
finally:
    conn.close()

🔸 Row Factories

# Access columns by name
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute("SELECT * FROM users LIMIT 1")
row = cursor.fetchone()
print(row["name"], row["age"])

🔸 Date/Time Handling

import datetime

# Register adapters and converters
sqlite3.register_adapter(datetime.datetime, lambda dt: dt.isoformat())
sqlite3.register_converter("timestamp", lambda dt: datetime.datetime.fromisoformat(dt.decode()))

conn = sqlite3.connect(":memory:", detect_types=sqlite3.PARSE_DECLTYPES)
conn.execute("CREATE TABLE events (id INTEGER, created_at timestamp)")

🔸 Best Practices

✓ Always use parameterized queries

✓ Use context managers for connections

✓ Enable foreign key support

✓ Handle transactions explicitly

✓ Close connections properly

✓ Use row factories for named access

🔹 Web Scraping with Python

🔸 Advanced BeautifulSoup

from bs4 import BeautifulSoup
import requests

# Configure session
session = requests.Session()
session.headers.update({"User-Agent": "Mozilla/5.0"})

# Handle pagination
for page in range(1, 4):
    url = f"https://example.com/page/{page}"
    response = session.get(url)
    soup = BeautifulSoup(response.text, "lxml")  # Using lxml parser 
    # CSS Selectors
    articles = soup.select("div.article > h2.title")
    for article in articles:
        print(article.get_text(strip=True))
    
    # Extract attributes and handle missing elements
    prices = soup.find_all("span", class_="price")
    for price in prices:
        print(price["data-value"] if price else "N/A")

🔸 Advanced Selenium

from selenium.webdriver import Chrome
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

driver = Chrome()
driver.get("https://example.com/login")

# Form interaction
driver.find_element(By.ID, "username").send_keys("user123")
driver.find_element(By.ID, "password").send_keys("pass123")
driver.find_element(By.XPATH, "//button[@type='submit']").click()

# Wait for elements
try:
    element = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.CLASS_NAME, "dashboard"))
    )
    print("Login successful")
except TimeoutError:
    print("Login failed")

# Execute JavaScript
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
driver.save_screenshot("page.png")
driver.quit()

🔸 Scrapy Framework

import scrapy

class QuoteSpider(scrapy.Spider):
    name = "quotes"
    start_urls = ["http://quotes.toscrape.com"]
    
    def parse(self, response):
        for quote in response.css("div.quote"):
            yield {
                "text": quote.css("span.text::text").get(),
                "author": quote.css("small.author::text").get(),
            }
        
        next_page = response.css("li.next a::attr(href)").get()
        if next_page:
            yield response.follow(next_page, self.parse)

🔸 Best Practices

✓ Respect robots.txt and website terms

✓ Use delays between requests (time.sleep(2))

✓ Rotate user agents and IP addresses

✓ Handle CAPTCHAs and JavaScript challenges

✓ Cache responses for development

# Proxies and headers
proxies = {"http": "http://10.10.1.10:3128"}
headers = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64)"}
response = requests.get(url, headers=headers, proxies=proxies)

🔸 Advanced Techniques

# Headless browsing
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument("--headless=new")
driver = Chrome(options=options)

# Handling JavaScript-rendered content
driver.get("https://example.com")
WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.CLASS_NAME, "dynamic-content"))
)

# Scraping tables
table = driver.find_element(By.TAG_NAME, "table")
rows = table.find_elements(By.TAG_NAME, "tr")
for row in rows:
    print([cell.text for cell in row.find_elements(By.TAG_NAME, "td")])

🔹 Data Science with Python

🔸 NumPy (Numerical Computing)

import numpy as np

# Array creation
arr = np.array([[1, 2], [3, 4]])  # 2D array
zeros = np.zeros((3, 3))          # 3x3 zero matrix
range_arr = np.arange(0, 10, 2)  # [0 2 4 6 8]

# Array operations
print(arr.shape)      # (2, 2)
print(arr.reshape(4)) # [1 2 3 4]
print(arr.T)          # Transpose
print(np.dot(arr, arr))  # Matrix multiplication

# Universal functions
print(np.sqrt(arr))     # Element-wise square root
print(np.sum(arr))      # 10
print(arr.mean(axis=0)) # Column means [2. 3.]

🔸 Pandas (Data Manipulation)

import pandas as pd

# DataFrame operations
df = pd.read_csv("data.csv")
print(df.head(2))           # First 2 rows
print(df.describe())        # Statistics
print(df[df['Age'] > 25])  # Filtering
print(df.groupby('City')['Salary'].mean())  # Aggregation

# Handling missing data
df['Age'] = df['Age'].fillna(df['Age'].mean())
df = df.dropna()

# Merging DataFrames
df1 = pd.DataFrame({'key': ['A', 'B'], 'val': [1, 2]})
df2 = pd.DataFrame({'key': ['A', 'B'], 'val': [3, 4]})
merged = pd.merge(df1, df2, on='key')

🔸 Matplotlib (Data Visualization)

import matplotlib.pyplot as plt

# Subplots and styling
fig, ax = plt.subplots(2, 1, figsize=(8, 6))
ax[0].plot([1, 2, 3], [2, 4, 3], 'ro-', label='Line 1')
ax[0].set_title('Sales Data')
ax[0].legend()

# Histogram
ax[1].hist(np.random.randn(1000), bins=30, alpha=0.5)
ax[1].set_xlabel('Values')
plt.tight_layout()
plt.savefig('plot.png')

🔸 Scikit-learn (Machine Learning)

from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split

# Model training
X = [[1], [2], [3], [4]]
y = [2, 4, 6, 8]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

model = LinearRegression()
model.fit(X_train, y_train)
print(f"R² Score: {model.score(X_test, y_test):.2f}")

🔸 Seaborn (Advanced Visualization)

import seaborn as sns

# Advanced plots
tips = sns.load_dataset("tips")
sns.jointplot(x="total_bill", y="tip", data=tips, kind="reg")
sns.pairplot(tips, hue="time")
plt.show()

🔸 Best Practices

✓ Use vectorized operations in NumPy

✓ Optimize pandas with proper dtypes

✓ Handle missing data early

✓ Normalize features for ML models

✓ Use virtual environments

# Install packages
# pip install numpy pandas matplotlib scikit-learn seaborn

🔹 GUI Programming with Python

🔸 Tkinter Advanced

import tkinter as tk
from tkinter import ttk, messagebox

class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Advanced Tkinter")
        self.geometry("400x300")
        
        # Widgets
        self.label = ttk.Label(self, text="Enter text:")
        self.entry = ttk.Entry(self)
        self.button = ttk.Button(self, text="Submit", command=self.on_submit)
        self.listbox = tk.Listbox(self)
        
        # Layout
        self.label.pack(pady=5)
        self.entry.pack(pady=5)
        self.button.pack(pady=5)
        self.listbox.pack(pady=5, fill=tk.BOTH, expand=True)
        
        # Menu
        menubar = tk.Menu(self)
        file_menu = tk.Menu(menubar, tearoff=0)
        file_menu.add_command(label="Exit", command=self.destroy)
        menubar.add_cascade(label="File", menu=file_menu)
        self.config(menu=menubar)
    
    def on_submit(self):
        text = self.entry.get()
        if text:
            self.listbox.insert(tk.END, text)
            self.entry.delete(0, tk.END)
        else:
            messagebox.showwarning("Input Error", "Please enter text")
if __name__ == "__main__":
    app = App()
    app.mainloop()

🔸 PyQt Advanced

from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, 
                            QLabel, QPushButton, QLineEdit, QListWidget)
from PyQt5.QtCore import Qt
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Advanced PyQt")
        self.setGeometry(100, 100, 400, 300)
        
        # Central widget
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        layout = QVBoxLayout()
        
        # Widgets
        self.label = QLabel("Enter text:")
        self.entry = QLineEdit()
        self.button = QPushButton("Submit")
        self.list_widget = QListWidget()
        
        # Layout
        layout.addWidget(self.label)
        layout.addWidget(self.entry)
        layout.addWidget(self.button)
        layout.addWidget(self.list_widget)
        central_widget.setLayout(layout)
        
        # Signals
        self.button.clicked.connect(self.on_submit)
        self.entry.returnPressed.connect(self.on_submit)
        
        # Menu
        menu = self.menuBar()
        file_menu = menu.addMenu("File")
        exit_action = file_menu.addAction("Exit")
        exit_action.triggered.connect(self.close)
    
    def on_submit(self):
        text = self.entry.text()
        if text:
            self.list_widget.addItem(text)
            self.entry.clear()
        else:
            self.statusBar().showMessage("Please enter text", 3000)
if __name__ == "__main__":
    app = QApplication([])
    window = MainWindow()
    window.show()
    app.exec_()

🔸 Best Practices

✓ Separate UI and business logic

✓ Use threading for long-running tasks

✓ Follow platform-specific design guidelines

✓ Implement error handling

✓ Use layout managers (grid/pack in Tkinter, QLayout in PyQt)

# Threading example for PyQt
from PyQt5.QtCore import QThread
class Worker(QThread):
    def run(self):
        # Long-running task
        self.sleep(5)
        
# Usage
worker = Worker()
worker.start()

🔸 Other GUI Libraries

Kivy: Cross-platform, mobile-friendly

from kivy.app import App
from kivy.uix.button import Button
class MyApp(App):
    def build(self):
        return Button(text='Hello Kivy')
MyApp().run()

PySimpleGUI: Simplified wrapper

import PySimpleGUI as sg
layout = [[sg.Text("Hello")], [sg.Button("OK")]]
window = sg.Window("Demo", layout)
event, values = window.read()

🔹 Machine Learning with Python

🔸 Scikit-learn Pipeline

from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
# Create pipeline
pipe = make_pipeline(
    StandardScaler(),
    SVC(kernel='rbf')
)
# Train and evaluate
pipe.fit(X_train, y_train)
score = pipe.score(X_test, y_test)

🔸 TensorFlow Training

# Model training
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])
history = model.fit(X_train, y_train, 
                    epochs=10,
                    validation_split=0.2)
# Evaluation
test_loss, test_acc = model.evaluate(X_test, y_test)
print(f"Test accuracy: {test_acc:.2f}")
# Save model
model.save("model.h5")

🔸 PyTorch Training Loop

import torch.optim as optim
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
for epoch in range(100):
    # Forward pass
    outputs = model(inputs)
    loss = criterion(outputs, labels)
    
    # Backward pass
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    
    if (epoch+1) % 10 == 0:
        print(f'Epoch {epoch+1}, Loss: {loss.item():.4f}')

🔸 Model Evaluation

from sklearn.metrics import classification_report
# Classification report
y_pred = model.predict(X_test)
print(classification_report(y_test, y_pred))
# Confusion matrix
from sklearn.metrics import confusion_matrix
print(confusion_matrix(y_test, y_pred))

🔸 Best Practices

✓ Split data into train/validation/test sets

✓ Normalize/standardize features

✓ Use cross-validation

✓ Monitor training with TensorBoard

✓ Version control your models

# Data splitting
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42)

🔸 Hyperparameter Tuning

from sklearn.model_selection import GridSearchCV
params = {'C': [0.1, 1, 10], 'gamma': [1, 0.1, 0.01]}
grid = GridSearchCV(SVC(), params, cv=5)
grid.fit(X_train, y_train)
print(f"Best params: {grid.best_params_}")

🔹 Cybersecurity with Python

🔸 Secure Password Hashing

import bcrypt
import secrets
# Generate secure password hash
password = "user_password".encode()
salt = bcrypt.gensalt(rounds=12)
hashed = bcrypt.hashpw(password, salt)
# Verify password
if bcrypt.checkpw(password, hashed):
    print("Access granted")

🔸 RSA Encryption

from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes
# Generate key pair
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
public_key = private_key.public_key()
# Encrypt with public key
message = b"Secret Message"
ciphertext = public_key.encrypt(
    message,
    padding.OAEP(
        mgf=padding.MGF1(algorithm=hashes.SHA256()),
        algorithm=hashes.SHA256(),
        label=None
    )
)
# Decrypt with private key
plaintext = private_key.decrypt(
    ciphertext,
    padding.OAEP(
        mgf=padding.MGF1(algorithm=hashes.SHA256()),
        algorithm=hashes.SHA256(),
        label=None
    )
)

🔸 Digital Signatures

from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import hashes
# Sign message
signature = private_key.sign(
    message,
    padding.PSS(
        mgf=padding.MGF1(hashes.SHA256()),
        salt_length=padding.PSS.MAX_LENGTH
    ),
    hashes.SHA256()
)
# Verify signature
try:
    public_key.verify(
        signature,
        message,
        padding.PSS(
            mgf=padding.MGF1(hashes.SHA256()),
            salt_length=padding.PSS.MAX_LENGTH
        ),
        hashes.SHA256()
    )
    print("Signature valid")
except InvalidSignature:
    print("Signature invalid")

🔸 SSL/TLS Verification

import requests
import ssl
# Secure requests
response = requests.get("https://api.example.com", verify=True)
# Create SSL context
context = ssl.create_default_context()
context.check_hostname = True
context.verify_mode = ssl.CERT_REQUIRED

🔸 Security Utilities

# Secure random generation
import secrets
token = secrets.token_urlsafe(32)  # 256-bit secure URL-safe token
# Input validation
import re
def sanitize_input(input_str):
    return re.sub(r'[^a-zA-Z0-9]', '', input_str)
# Secret management
from getpass import getpass

🔸 Best Practices

✓ Use bcrypt/argon2 for password storage

✓ Never store secrets in code

✓ Validate all user inputs

✓ Use HTTPS for network communication

✓ Rotate encryption keys regularly

# Dependency security check
# pip install safety
# safety check