Python Complete Guide
This guide covers Python fundamentals, Generative AI libraries, and data science tools with practical examples.
Core Python Concepts
Variables and Data Types
Variables are containers for storing data values. Python is dynamically typed, meaning you don't need to declare variable types explicitly.
Data Types are classifications that specify which type of value a variable can hold. Python has several built-in data types:
- int: Integer numbers (e.g., 42, -10)
- float: Decimal numbers (e.g., 3.14, -0.5)
- str: Text strings (e.g., "Hello")
- bool: Boolean values (True or False)
- list: Ordered, mutable collections
- tuple: Ordered, immutable collections
- dict: Key-value pairs (dictionaries)
- set: Unordered collections of unique elements
Python supports multiple data types including integers, floats, strings, booleans, lists, tuples, dictionaries, and sets.
# Basic data types
integer_var = 42
float_var = 3.14
string_var = "Hello, Python!"
boolean_var = True
# Collections
list_example = [1, 2, 3, 4, 5]
tuple_example = (1, 2, 3)
dict_example = {"name": "Alice", "age": 30}
set_example = {1, 2, 3, 4}
# Type checking
print(type(integer_var)) # <class 'int'>
print(type(list_example)) # <class 'list'>
# Type conversion
str_num = "123"
int_num = int(str_num)
float_num = float(str_num)
str_from_int = str(int_num)
# String operations
text = "Python Programming"
print(text.lower())
print(text.upper())
print(text.split())
print(text.replace("Python", "Java"))
print(text[0:6]) # SlicingControl Flow
Control flow determines the order in which code executes. It allows your program to make decisions and repeat actions.
Conditional Statements (if-else): Execute different code blocks based on conditions. The if statement checks a condition, elif (else if) checks additional conditions, and else handles all other cases.
Loops: Repeat code multiple times:
- for loop: Iterates over a sequence (list, tuple, string, range)
- while loop: Repeats as long as a condition is True
Comprehensions: Concise way to create lists, dictionaries, or sets from existing iterables. They combine loops and conditional logic in a single line.
# If-else statements
age = 18
if age >= 18:
print("Adult")
elif age >= 13:
print("Teenager")
else:
print("Child")
# Ternary operator
status = "Adult" if age >= 18 else "Minor"
# Multiple conditions
score = 85
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
else:
grade = "F"
# Logical operators
has_license = True
age = 20
if has_license and age >= 18:
print("Can drive")# For loops
for i in range(5):
print(i)
# While loops
count = 0
while count < 5:
print(count)
count += 1
# Loop with enumerate
fruits = ["apple", "banana", "cherry"]
for index, fruit in enumerate(fruits):
print(f"{index}: {fruit}")
# Loop with zip
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]
for name, age in zip(names, ages):
print(f"{name} is {age} years old")
# Break and continue
for i in range(10):
if i == 3:
continue # Skip 3
if i == 7:
break # Stop at 7
print(i)
# Nested loops
for i in range(3):
for j in range(3):
print(f"({i}, {j})")# List comprehension
squares = [x**2 for x in range(10)]
evens = [x for x in range(20) if x % 2 == 0]
# Nested list comprehension
matrix = [[i*j for j in range(3)] for i in range(3)]
# Dictionary comprehension
square_dict = {x: x**2 for x in range(5)}
filtered_dict = {k: v for k, v in square_dict.items() if v > 5}
# Set comprehension
even_squares = {x**2 for x in range(10) if x % 2 == 0}
# Generator expression
gen = (x**2 for x in range(1000000))
print(next(gen)) # 0
print(next(gen)) # 1Functions and Lambda
Functions are reusable blocks of code that perform specific tasks. They help organize code, reduce repetition, and make programs more maintainable.
Key Concepts:
- def keyword: Used to define a function
- Parameters: Variables that receive values when the function is called
- Return statement: Sends a value back to the caller
- Default parameters: Pre-set values for parameters
- **Variable arguments (*args, kwargs): Accept any number of arguments
Lambda Functions: Anonymous, single-expression functions defined using the lambda keyword. Useful for short, simple operations.
Higher-Order Functions: Functions that take other functions as arguments or return functions:
- map(): Applies a function to all items in an iterable
- filter(): Filters items based on a condition
- reduce(): Reduces an iterable to a single value
Decorators: Functions that modify or enhance other functions without changing their code. They use the @ symbol and are commonly used for logging, timing, or access control.
# Standard function
def greet(name, greeting="Hello"):
"""Greet a person with a custom message."""
return f"{greeting}, {name}!"
result = greet("Alice")
print(result)
# Multiple return values
def get_stats(numbers):
return min(numbers), max(numbers), sum(numbers)/len(numbers)
min_val, max_val, avg = get_stats([1, 2, 3, 4, 5])
# Variable arguments
def sum_all(*args):
return sum(args)
print(sum_all(1, 2, 3, 4, 5))
# Keyword arguments
def create_profile(**kwargs):
return kwargs
profile = create_profile(name="Alice", age=30, city="NYC")
# Lambda functions
square = lambda x: x**2
add = lambda x, y: x + y
multiply = lambda x, y, z: x * y * z
# Higher-order functions
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x**2, numbers))
evens = list(filter(lambda x: x % 2 == 0, numbers))
from functools import reduce
product = reduce(lambda x, y: x * y, numbers)
# Decorators
def timing_decorator(func):
import time
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"Execution time: {end - start:.4f}s")
return result
return wrapper
@timing_decorator
def slow_function():
import time
time.sleep(1)
return "Done"
# Multiple decorators
def uppercase_decorator(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result.upper()
return wrapper
@uppercase_decorator
@timing_decorator
def get_message():
return "hello world"
# Closure example
def outer_function(x):
def inner_function(y):
return x + y
return inner_function
add_5 = outer_function(5)
print(add_5(10)) # 15Object-Oriented Programming
Object-Oriented Programming (OOP) is a programming paradigm that organizes code around objects and classes, making it easier to manage complex programs.
Core OOP Concepts:
Class: A blueprint for creating objects. Defines attributes (data) and methods (functions) that objects will have.
Object: An instance of a class. Each object has its own set of attributes.
Inheritance: Allows a class (child) to inherit attributes and methods from another class (parent), promoting code reuse.
Encapsulation: Bundling data and methods that work on that data within a class, hiding internal details.
Polymorphism: The ability of different classes to be treated as instances of the same class through inheritance.
Key Features:
__init__(): Constructor method called when creating a new objectself: Refers to the current instance of the class@property: Creates getter/setter methods for controlled attribute access@staticmethod: Methods that don't access instance or class data@classmethod: Methods that work with class-level data- Magic methods (
__str__,__add__, etc.): Special methods that define object behavior
# Class definition
class Animal:
# Class variable
kingdom = "Animalia"
def __init__(self, name, species):
# Instance variables
self.name = name
self.species = species
def make_sound(self):
return "Some sound"
def __str__(self):
return f"{self.name} is a {self.species}"
def __repr__(self):
return f"Animal('{self.name}', '{self.species}')"
# Inheritance
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name, "Dog")
self.breed = breed
def make_sound(self):
return "Woof!"
def fetch(self):
return f"{self.name} is fetching!"
# Multiple inheritance
class Flyable:
def fly(self):
return "Flying!"
class Bird(Animal, Flyable):
def __init__(self, name):
super().__init__(name, "Bird")
def make_sound(self):
return "Chirp!"
# Usage
dog = Dog("Buddy", "Golden Retriever")
print(dog.make_sound()) # Woof!
print(dog.fetch()) # Buddy is fetching!
bird = Bird("Tweety")
print(bird.fly()) # Flying!
# Property decorators
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:
raise ValueError("Radius cannot be negative")
self._radius = value
@property
def area(self):
import math
return math.pi * self._radius ** 2
@property
def circumference(self):
import math
return 2 * math.pi * self._radius
circle = Circle(5)
print(circle.area)
# Class methods and static methods
class MathOperations:
@staticmethod
def add(x, y):
return x + y
@classmethod
def create_from_string(cls, string):
# Create instance from string
return cls()
@staticmethod
def multiply(x, y):
return x * y
# Magic methods
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
def __str__(self):
return f"Vector({self.x}, {self.y})"
def __len__(self):
import math
return int(math.sqrt(self.x**2 + self.y**2))
v1 = Vector(1, 2)
v2 = Vector(3, 4)
v3 = v1 + v2
print(v3) # Vector(4, 6)
# Abstract base classes
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)Exception Handling
Exception Handling allows you to gracefully handle errors and unexpected situations in your code without crashing the program.
Key Components:
- try: Block of code that might raise an exception
- except: Handles specific exceptions that occur in the try block
- else: Executes if no exception occurs
- finally: Always executes, regardless of exceptions (used for cleanup)
- raise: Manually triggers an exception
Custom Exceptions: Create your own exception classes by inheriting from the Exception class to handle specific error conditions in your application.
Context Managers: Objects that set up and clean up resources automatically using with statement. Commonly used for file operations, database connections, or any resource that needs cleanup.
# Try-except blocks
try:
result = 10 / 0
except ZeroDivisionError as e:
print(f"Error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
else:
print("No exceptions occurred")
finally:
print("This always executes")
# Multiple exceptions
try:
value = int("abc")
except (ValueError, TypeError) as e:
print(f"Conversion error: {e}")
# Custom exceptions
class ValidationError(Exception):
def __init__(self, message, code=None):
self.message = message
self.code = code
super().__init__(self.message)
class InsufficientFundsError(Exception):
pass
def validate_age(age):
if age < 0:
raise ValidationError("Age cannot be negative", code="NEGATIVE_AGE")
if age > 150:
raise ValidationError("Age too high", code="EXCESSIVE_AGE")
return age
try:
validate_age(-5)
except ValidationError as e:
print(f"Validation failed: {e.message} (Code: {e.code})")
# Context managers
class FileManager:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
if exc_type is not None:
print(f"Exception occurred: {exc_val}")
return False
# Using with statement
with FileManager('example.txt', 'w') as f:
f.write('Hello, World!')
# Context manager with contextlib
from contextlib import contextmanager
@contextmanager
def managed_resource():
# Setup
resource = "Resource acquired"
try:
yield resource
finally:
# Cleanup
print("Resource released")
with managed_resource() as r:
print(r)
# Assertion
def divide(a, b):
assert b != 0, "Divisor cannot be zero"
return a / b
# Exception chaining
try:
try:
result = 1 / 0
except ZeroDivisionError as e:
raise ValueError("Invalid calculation") from e
except ValueError as e:
print(f"Error: {e}")
print(f"Original: {e.__cause__}")File Operations
File Operations allow you to read from and write to files on your system, enabling data persistence and file manipulation.
File Modes:
'r': Read mode (default)'w': Write mode (overwrites existing content)'a': Append mode (adds to existing content)'b': Binary mode (for non-text files)'+': Read and write mode
Best Practice: Always use the with statement for file operations. It automatically closes files even if errors occur, preventing resource leaks.
CSV Files: Comma-Separated Values files are a common format for tabular data. Python's csv module provides easy reading and writing.
JSON Files: JavaScript Object Notation is a popular format for data exchange. Python's json module handles serialization (converting Python objects to JSON) and deserialization (JSON to Python objects).
# Reading files
# Method 1: Using with statement (recommended)
with open('file.txt', 'r') as f:
content = f.read()
print(content)
# Method 2: Reading line by line
with open('file.txt', 'r') as f:
for line in f:
print(line.strip())
# Method 3: Read all lines into list
with open('file.txt', 'r') as f:
lines = f.readlines()
# Writing files
with open('output.txt', 'w') as f:
f.write('Hello, World!\n')
f.write('Second line\n')
# Appending to files
with open('output.txt', 'a') as f:
f.write('Appended line\n')
# Working with CSV
import csv
# Reading CSV
with open('data.csv', 'r') as f:
reader = csv.reader(f)
for row in reader:
print(row)
# Writing CSV
data = [
['Name', 'Age', 'City'],
['Alice', 25, 'NYC'],
['Bob', 30, 'LA']
]
with open('output.csv', 'w', newline='') as f:
writer = csv.writer(f)
writer.writerows(data)
# Using DictReader and DictWriter
with open('data.csv', 'r') as f:
reader = csv.DictReader(f)
for row in reader:
print(row['Name'], row['Age'])
with open('output.csv', 'w', newline='') as f:
fieldnames = ['Name', 'Age', 'City']
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerow({'Name': 'Alice', 'Age': 25, 'City': 'NYC'})
# Working with JSON
import json
# Reading JSON
with open('data.json', 'r') as f:
data = json.load(f)
# Writing JSON
data = {'name': 'Alice', 'age': 30, 'city': 'NYC'}
with open('output.json', 'w') as f:
json.dump(data, f, indent=4)
# JSON string operations
json_string = json.dumps(data)
parsed_data = json.loads(json_string)
# Binary file operations
with open('image.jpg', 'rb') as f:
binary_data = f.read()
with open('copy.jpg', 'wb') as f:
f.write(binary_data)Modules and Packages
Modules are Python files containing reusable code (functions, classes, variables). They help organize large programs into manageable pieces.
Packages are directories containing multiple modules and a special __init__.py file. They create a hierarchical structure for organizing related modules.
Importing:
import module: Import entire modulefrom module import function: Import specific itemsimport module as alias: Create a shorter namefrom module import *: Import everything (not recommended)
Benefits:
- Code reusability across projects
- Better organization and namespace management
- Easier maintenance and collaboration
# Importing modules
import math
import os
import sys
from datetime import datetime, timedelta
from collections import defaultdict, Counter
import random as rnd
# Using imported modules
print(math.sqrt(16))
print(os.getcwd())
print(datetime.now())
# Creating your own module
# File: mymodule.py
def greet(name):
return f"Hello, {name}!"
PI = 3.14159
class Calculator:
@staticmethod
def add(x, y):
return x + y
# Using your module
# import mymodule
# print(mymodule.greet("Alice"))
# print(mymodule.PI)
# Package structure
# mypackage/
# __init__.py
# module1.py
# module2.py
# subpackage/
# __init__.py
# module3.py
# Importing from packages
# from mypackage import module1
# from mypackage.subpackage import module3
# __init__.py content
# __all__ = ['module1', 'module2']
# Conditional imports
try:
import numpy as np
HAS_NUMPY = True
except ImportError:
HAS_NUMPY = False
print("NumPy not available")
# Dynamic imports
module_name = "math"
math_module = __import__(module_name)Iterators and Generators
Iterators are objects that implement the iterator protocol with __iter__() and __next__() methods. They allow you to traverse through a collection one element at a time.
Generators are special functions that use yield instead of return. They generate values on-the-fly and are memory efficient for large datasets.
Key Differences:
- Iterators: Must implement both
__iter__()and__next__(), maintain state manually - Generators: Automatically implement iterator protocol, state maintained by Python
Benefits:
- Memory efficient (don't store all values in memory)
- Infinite sequences possible
- Lazy evaluation (compute values only when needed)
Generator Expressions: Similar to list comprehensions but use parentheses and create generators instead of lists.
itertools Module: Provides efficient iterator building blocks for creating complex iterators.
# Iterator example
class Countdown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self
def __next__(self):
if self.current <= 0:
raise StopIteration
self.current -= 1
return self.current + 1
counter = Countdown(5)
for num in counter:
print(num)
# Generator function
def fibonacci(n):
a, b = 0, 1
count = 0
while count < n:
yield a
a, b = b, a + b
count += 1
for num in fibonacci(10):
print(num)
# Generator expression
squares_gen = (x**2 for x in range(10))
print(next(squares_gen))
print(next(squares_gen))
# Generator with send()
def echo():
while True:
value = yield
if value is not None:
print(f"Received: {value}")
gen = echo()
next(gen) # Prime the generator
gen.send("Hello")
gen.send("World")
# Infinite generator
def infinite_sequence():
num = 0
while True:
yield num
num += 1
# Generator pipeline
def integers():
i = 0
while True:
yield i
i += 1
def squared(seq):
for i in seq:
yield i * i
def take(n, seq):
result = []
for i, val in enumerate(seq):
if i >= n:
break
result.append(val)
return result
# Using itertools
from itertools import count, cycle, repeat, chain, combinations, permutations
# Infinite iterators
counter = count(start=10, step=2) # 10, 12, 14, ...
cycler = cycle([1, 2, 3]) # 1, 2, 3, 1, 2, 3, ...
repeater = repeat(10, times=3) # 10, 10, 10
# Combinatoric iterators
items = ['A', 'B', 'C']
print(list(combinations(items, 2))) # [('A', 'B'), ('A', 'C'), ('B', 'C')]
print(list(permutations(items, 2))) # All permutations
# Chaining iterators
list1 = [1, 2, 3]
list2 = [4, 5, 6]
chained = chain(list1, list2)Generative AI with Python
Explore popular Python libraries for Generative AI, including OpenAI, Anthropic Claude, LangChain, Hugging Face Transformers, and LlamaIndex.
RAG Implementation with Hybrid Search
A comprehensive guide to building production-ready Retrieval-Augmented Generation (RAG) systems using Pinecone, OpenSearch, Sanity.io, and OpenAI.