Functions, Methods & Classes Deep Dive
Master functions, parameters, methods, classes, inheritance, polymorphism, and production‑grade OOP patterns – with detailed examples, solved problems, and practice challenges.
📌 What you'll learn: Functions are the building blocks of any Python program. Methods bring functions inside classes to manipulate object data. Classes let you model real‑world entities, promote code reuse, and structure large applications. This page covers it all: from simple def to abstract base classes and production patterns.
🎯 Goal: Write clean, reusable, and extensible code using the full power of Python's function and class mechanics — whether you're a beginner or preparing for production systems.
Functions
Parameters
Methods
Classes
Inheritance
Advanced OOP
Production Patterns
Why Functions, Methods & Classes Matter
Functions eliminate code duplication, make programs modular, and improve readability. Classes group related data and behavior, allowing you to model complex systems naturally. Methods encapsulate operations that belong to specific objects.
Without functions, even small scripts become unmaintainable spaghetti code. Without classes, managing complex state (like a user account or a database connection) becomes error‑prone and repetitive.
Well‑designed classes and functions lead to testable, scalable, and maintainable codebases. Factories, singletons, dependency injection, and immutable dataclasses are used daily in production to reduce bugs and improve performance.
1 Functions – The Fundamental Building Block
Define a function with def, optionally return a value. Use docstrings to document behavior. Functions are first‑class objects: you can pass them as arguments, store them in lists, and return them from other functions.
def greet(name: str) -> str:
"""Return a friendly greeting."""
return f"Hello, {name}!"
# First‑class function example
def apply(func, value):
return func(value)
print(apply(greet, "World")) # Hello, World!
return to give back a value. If no return, function returns None. For reusable code, keep functions pure (no side effects) when possible.2 Parameters – Positional, Keyword, *args, **kwargs
Python offers a rich parameter system. Use positional‑only (/), keyword‑only (*), default values, and variable‑length arguments.
def config(host, port=8080, *, timeout=10):
"""host is positional, port has default, timeout is keyword‑only."""
print(f"{host}:{port}, timeout={timeout}")
# Valid calls
config("localhost")
config("localhost", 3000, timeout=20)
config("localhost", timeout=5) # port defaults to 8080
# Variable arguments
def log_all(*args, **kwargs):
for a in args: print(a)
for k,v in kwargs.items(): print(f"{k}={v}")
log_all("error", "warning", user="admin", code=500)
None and initialize inside the function.3 Methods – Instance, Class, Static & Dunder
Methods are functions defined inside a class. The first parameter is usually self (instance). Use @classmethod for methods that need the class (first parameter is cls). @staticmethod for utility functions that don't need instance/class data.
class Product:
discount = 0.1 # class attribute
def __init__(self, name, price):
self.name = name
self.price = price
def final_price(self): # instance method
return self.price * (1 - Product.discount)
@classmethod
def update_discount(cls, new_discount):
cls.discount = new_discount
@staticmethod
def is_expensive(price):
return price > 1000
Dunder (double underscore) methods like __str__, __repr__, __eq__ let you control how objects behave with built‑ins.
4 Classes – Blueprints for Objects
A class defines attributes (data) and methods (behavior). Use __init__ to initialize instance attributes. Class attributes are shared across all instances.
class BankAccount:
bank_name = "National Bank" # class attribute
def __init__(self, owner, balance=0):
self.owner = owner # instance attribute
self.balance = balance
def deposit(self, amount):
self.balance += amount
return self.balance
acc1 = BankAccount("Alice", 100)
acc2 = BankAccount("Bob")
print(acc1.bank_name) # National Bank
acc1.deposit(50) # 150
5 Inheritance & Polymorphism
Create a new class (subclass) from an existing one (superclass). Use super() to call the parent's methods. Override methods to provide specialized behavior.
class Animal:
def speak(self):
return "Some sound"
class Dog(Animal):
def speak(self): # override
return "Woof!"
def animal_sound(animal: Animal):
print(animal.speak()) # polymorphism
animal_sound(Dog()) # Woof!
animal_sound(Animal()) # Some sound
from abc import ABC, abstractmethod.6 Advanced OOP – Properties, Dataclasses, Mixins
Properties allow getter/setter logic while using attribute syntax. Dataclasses auto‑generate __init__, __repr__, etc. Mixins add reusable functionality via multiple inheritance.
from dataclasses import dataclass
class Thermometer:
def __init__(self, celsius):
self._celsius = celsius
@property
def fahrenheit(self):
return self._celsius * 9/5 + 32
@dataclass(frozen=True) # immutable
class Point:
x: float
y: float
7 Production‑Grade Patterns
Use type hints for clarity, @dataclass for data containers, factory functions for complex object creation, and context managers for resource safety.
class DatabaseConnection:
def __init__(self, url):
self.url = url
def __enter__(self):
print("Connecting...")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("Closing connection")
return False # don't suppress exceptions
with DatabaseConnection("postgres://...") as conn:
print("Running queries")
Solved Practice Problems
🔰 Easy (3 problems)
def greet(name="World"):
return f"Hello, {name}!"
print(greet("Alice")) # Hello, Alice!class Rectangle:
def __init__(self, w, h):
self.w = w
self.h = h
def area(self):
return self.w * self.hclass Dog:
def bark(self):
print("Woof!")
d = Dog()
d.bark()⚡ Intermediate (3 problems)
class Circle:
def __init__(self, radius):
self.radius = radius
@classmethod
def from_diameter(cls, d):
return cls(d/2)class BankAccount:
def __init__(self, balance, min_balance):
self.balance = balance
self.min_balance = min_balance
def withdraw(self, amount):
if self.balance - amount < self.min_balance:
raise ValueError("Below minimum balance")
self.balance -= amountclass Temperature:
def __init__(self, celsius):
self._c = celsius
@property
def fahrenheit(self):
return self._c * 9/5 + 32🚀 Advanced (3 problems)
from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
x: float
y: floatclass FileWriter:
def __init__(self, filename):
self.filename = filename
def __enter__(self):
self.file = open(self.filename, 'w')
return self.file
def __exit__(self, *args):
self.file.close()from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2Unsolved Practice Problems
🔰 Easy (3 problems)
1. Greet User Function
Write a function greet_user(name) that returns "Hello, {name}". If no name given, default to "Guest".
2. Calculator Class
Create a class Calculator with methods add(a,b), subtract(a,b). Don't use instance attributes.
3. Student Class
Define a Student class with attributes name and grades (list). Add a method average() that returns the average grade.
⚡ Intermediate (3 problems)
4. Class with @classmethod counter
Implement a Person class that counts how many instances have been created using a class variable and @classmethod.
5. Inheritance: Vehicle and Car
Create Vehicle with method move(). Then Car subclass that overrides move() to print "Driving".
6. Dataclass with Validation
Write a dataclass Email with fields address and subject. Validate that address contains '@' in __post_init__.
🚀 Advanced (3 problems)
7. Context Manager for Timer
Build a Timer context manager that prints the elapsed time when exiting the with block.
8. Mixin for Logging
Create a mixin class LoggingMixin that provides a log() method. Use it in a Database class.
9. Factory Function for Shapes
Write a function create_shape(shape_type, *args) that returns a Circle or Rectangle object based on input.