backend / python oop / 02_classes_in_python.md

Classes in Python

4 interview angles 5 min read source

Classes in Python

A class is a blueprint or template for creating objects. It defines the structure and behavior that objects of that class will have. Classes are fundamental to Object-Oriented Programming (OOP) and provide a way to organize code into reusable, logical units.


Basic Class Definition

class Person:
    """A simple class representing a person."""

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def greet(self):
        return f"Hello, my name is {self.name} and I am {self.age} years old."

# Creating an instance
person = Person("Alice", 30)
print(person.greet())  # Output: Hello, my name is Alice and I am 30 years old.

Class Components

1. Class Name

  • Follows Python naming conventions (PascalCase)
  • Should be descriptive and meaningful

2. Class Variables (Class Attributes)

  • Shared among all instances of the class
  • Defined at the class level
class Car:
    # Class variable
    wheels = 4

    def __init__(self, brand, model):
        self.brand = brand
        self.model = model

car1 = Car("Toyota", "Camry")
car2 = Car("Honda", "Civic")

print(car1.wheels)  # 4
print(car2.wheels)  # 4
print(Car.wheels)   # 4

3. Instance Variables (Instance Attributes)

  • Unique to each instance
  • Defined in __init__ method using self
class Student:
    def __init__(self, name, student_id):
        self.name = name          # Instance variable
        self.student_id = student_id  # Instance variable
        self.grades = []          # Instance variable

4. Methods

  • Functions defined within a class
  • Can be instance methods, class methods, or static methods

Types of Methods

Instance Methods

  • Most common type of method
  • Take self as the first parameter
  • Can access and modify instance attributes
class BankAccount:
    def __init__(self, balance):
        self.balance = balance

    def deposit(self, amount):
        self.balance += amount
        return f"Deposited ${amount}. New balance: ${self.balance}"

    def withdraw(self, amount):
        if self.balance >= amount:
            self.balance -= amount
            return f"Withdrew ${amount}. New balance: ${self.balance}"
        else:
            return "Insufficient funds"

Class Methods

  • Use @classmethod decorator
  • Take cls as the first parameter
  • Can access class variables but not instance variables
class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    @classmethod
    def from_string(cls, date_string):
        year, month, day = map(int, date_string.split('-'))
        return cls(year, month, day)

    @classmethod
    def today(cls):
        import datetime
        today = datetime.date.today()
        return cls(today.year, today.month, today.day)

# Using class methods
date1 = Date.from_string("2023-12-25")
date2 = Date.today()

Static Methods

  • Use @staticmethod decorator
  • Don’t take self or cls as parameters
  • Cannot access class or instance variables
class MathUtils:
    @staticmethod
    def add(x, y):
        return x + y

    @staticmethod
    def multiply(x, y):
        return x * y

    @staticmethod
    def is_even(num):
        return num % 2 == 0

# Using static methods
result = MathUtils.add(5, 3)  # 8
is_even = MathUtils.is_even(10)  # True

Access Modifiers

Python doesn’t have strict access modifiers like other languages, but uses naming conventions:

Public Attributes

  • No special prefix
  • Accessible from anywhere
class Person:
    def __init__(self, name):
        self.name = name  # Public attribute

Protected Attributes

  • Single underscore prefix _
  • Convention indicating “internal use”
class Person:
    def __init__(self, name):
        self._name = name  # Protected attribute (convention)

Private Attributes

  • Double underscore prefix __
  • Name mangling prevents direct access
class Person:
    def __init__(self, name):
        self.__name = name  # Private attribute

    def get_name(self):
        return self.__name

person = Person("Alice")
# person.__name  # AttributeError
print(person.get_name())  # Alice

Constructor and Destructor

__init__ Method (Constructor)

  • Called automatically when creating an instance
  • Used for initialization
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
        self.area = width * height  # Calculate area during initialization

__del__ Method (Destructor)

  • Called when object is about to be destroyed
  • Used for cleanup
class FileHandler:
    def __init__(self, filename):
        self.filename = filename
        self.file = open(filename, 'r')

    def __del__(self):
        if hasattr(self, 'file'):
            self.file.close()
            print(f"File {self.filename} closed")

Class Inheritance

Classes can inherit from other classes:

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        pass

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

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

dog = Dog("Buddy")
cat = Cat("Whiskers")

print(dog.speak())  # Buddy says Woof!
print(cat.speak())  # Whiskers says Meow!

Special Methods (Magic Methods)

Classes can define special methods for custom behavior:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return f"Point({self.x}, {self.y})"

    def __repr__(self):
        return f"Point({self.x}, {self.y})"

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y)

p1 = Point(1, 2)
p2 = Point(3, 4)
p3 = p1 + p2
print(p3)  # Point(4, 6)

Summary Table

Component Description Example
Class Variable Shared among all instances wheels = 4
Instance Variable Unique to each instance self.name = name
Instance Method Method that uses self def greet(self):
Class Method Method that uses cls @classmethod def create(cls):
Static Method Method that uses neither @staticmethod def utility():
Constructor __init__ method def __init__(self, name):
Destructor __del__ method def __del__(self):

Key Interview Points

  1. Classes are blueprints for creating objects
  2. self refers to the instance of the class
  3. __init__ is the constructor method
  4. Class variables are shared, instance variables are unique
  5. Methods can be instance, class, or static
  6. Inheritance allows code reuse and hierarchy
  7. Magic methods customize object behavior
  8. Access modifiers are conventions, not enforced

Understanding classes is fundamental to Python OOP and essential for writing maintainable, reusable code!

Interview angle

  • “Class attribute versus instance attribute?” — a class attribute is shared by every instance; assigning to self.x creates an instance attribute that shadows it. The classic bug is a mutable class attribute like a list, where every instance appends to the same object.
  • @staticmethod, @classmethod, or a plain method?” — plain when it uses self; @classmethod when it needs the class, which is what makes alternative constructors work correctly under inheritance (cls is the subclass); @staticmethod when it uses neither and is just namespaced with the class.
  • “Why does @classmethod matter for factories?”cls(...) constructs the actual subclass, so Subclass.from_json(...) returns a Subclass. Hardcoding the class name in a @staticmethod returns the base class and silently breaks inheritance.
  • “What does a dataclass save you?” — generated __init__, __repr__ and __eq__, plus frozen=True for immutability and hashability. Use field(default_factory=list) for mutable defaults — a bare [] is the same shared-object bug as in a function signature.