Devcoder.io
Devcoder Knowledge Base

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])  # Slicing

Control 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))  # 1

Functions 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))  # 15

Object-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 object
  • self: 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 module
  • from module import function: Import specific items
  • import module as alias: Create a shorter name
  • from 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)

On this page