foreach-ui logo
codeLanguages
account_treeDSA

Quick Actions

quizlock Random Quiz
trending_uplock Progress
  • 1
  • 2
  • 3
  • 4
  • quiz
Python
  • Understand and implement special methods (__init__, __str__, etc.)
  • Create objects that behave like built-in types
  • Implement comparison and arithmetic operators
  • Use context managers and iterators

Special Methods and Properties

Have you ever wondered how len(my_list) works, or why you can add two custom objects with +? Behind the scenes, Python uses special methods (also called dunder methods because they start and end with double underscores __).

These methods allow your objects to behave like built-in types. Let's unlock the magic.

String Representations

str vs repr

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def __str__(self):
        """User-friendly string representation."""
        return f"{self.name}, age {self.age}"
    
    def __repr__(self):
        """Developer-friendly representation (unambiguous)."""
        return f"Person(name='{self.name}', age={self.age})"

person = Person("Alice", 30)

print(person)        # Alice, age 30 (__str__)
print(repr(person))  # Person(name='Alice', age=30) (__repr__)

# In interactive mode, __repr__ is used
person  # Person(name='Alice', age=30)

Rule of thumb:

  • __str__: For end users (readable)
  • __repr__: For developers (unambiguous, could recreate object)

Arithmetic Operators

Make your objects work with +, -, *, etc.:

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __add__(self, other):
        """Vector addition: self + other"""
        return Vector(self.x + other.x, self.y + other.y)
    
    def __sub__(self, other):
        """Vector subtraction: self - other"""
        return Vector(self.x - other.x, self.y - other.y)
    
    def __mul__(self, scalar):
        """Scalar multiplication: self * scalar"""
        return Vector(self.x * scalar, self.y * scalar)
    
    def __rmul__(self, scalar):
        """Right multiplication: scalar * self"""
        return self.__mul__(scalar)
    
    def __str__(self):
        return f"Vector({self.x}, {self.y})"

# Usage
v1 = Vector(1, 2)
v2 = Vector(3, 4)

print(v1 + v2)    # Vector(4, 6)
print(v2 - v1)    # Vector(2, 2)
print(v1 * 3)     # Vector(3, 6)
print(2 * v1)     # Vector(2, 4) (__rmul__)

Comparison Operators

Implement <, >, ==, etc.:

class Student:
    def __init__(self, name, grade):
        self.name = name
        self.grade = grade
    
    def __eq__(self, other):
        """self == other"""
        return self.grade == other.grade
    
    def __lt__(self, other):
        """self < other"""
        return self.grade < other.grade
    
    def __le__(self, other):
        """self <= other"""
        return self.grade <= other.grade
    
    def __str__(self):
        return f"{self.name}: {self.grade}"

# Usage
alice = Student("Alice", 85)
bob = Student("Bob", 92)
charlie = Student("Charlie", 85)

print(alice == charlie)  # True (same grade)
print(alice < bob)       # True (85 < 92)
print(bob <= alice)      # False (92 <= 85)

# Can now sort students
students = [bob, alice, charlie]
students.sort()  # Uses __lt__
for student in students:
    print(student)  # Alice: 85, Charlie: 85, Bob: 92

Container Methods

Make your objects work like lists or dictionaries:

class ShoppingCart:
    def __init__(self):
        self.items = {}
    
    def __setitem__(self, key, value):
        """cart[key] = value"""
        self.items[key] = value
    
    def __getitem__(self, key):
        """cart[key]"""
        return self.items[key]
    
    def __delitem__(self, key):
        """del cart[key]"""
        del self.items[key]
    
    def __len__(self):
        """len(cart)"""
        return len(self.items)
    
    def __iter__(self):
        """for item in cart:"""
        return iter(self.items)
    
    def __contains__(self, key):
        """key in cart"""
        return key in self.items

# Usage
cart = ShoppingCart()
cart["apple"] = 5
cart["banana"] = 3

print(len(cart))           # 2
print("apple" in cart)     # True
print(cart["banana"])      # 3

for item in cart:
    print(f"{item}: {cart[item]}")

Context Managers

Make your objects work with with statements:

class DatabaseConnection:
    def __init__(self, db_name):
        self.db_name = db_name
        self.connected = False
    
    def __enter__(self):
        """Called when entering 'with' block."""
        self.connected = True
        print(f"Connected to {self.db_name}")
        return self  # This becomes the 'as' variable
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        """Called when exiting 'with' block."""
        self.connected = False
        print(f"Disconnected from {self.db_name}")
        # Return False to propagate exceptions, True to suppress
    
    def query(self, sql):
        if not self.connected:
            raise RuntimeError("Not connected to database")
        return f"Results for: {sql}"

# Usage
with DatabaseConnection("mydb") as db:
    results = db.query("SELECT * FROM users")
    print(results)

# Connection automatically closed here

Callable Objects

Make your objects callable like functions:

class Polynomial:
    def __init__(self, coefficients):
        self.coefficients = coefficients  # [a, b, c] for ax^2 + bx + c
    
    def __call__(self, x):
        """Make object callable: poly(x)"""
        result = 0
        for power, coeff in enumerate(self.coefficients):
            result += coeff * (x ** power)
        return result
    
    def __str__(self):
        terms = []
        for power, coeff in enumerate(self.coefficients):
            if coeff == 0:
                continue
            if power == 0:
                terms.append(str(coeff))
            elif power == 1:
                terms.append(f"{coeff}x")
            else:
                terms.append(f"{coeff}x^{power}")
        return " + ".join(reversed(terms))

# Usage
poly = Polynomial([1, 2, 3])  # x^2 + 2x + 3
print(poly)                   # 3 + 2x + 1x^2
print(poly(0))                # 3
print(poly(1))                # 6
print(poly(2))                # 15

Custom List

class SortedList:
    """A list that keeps itself sorted."""
    
    def __init__(self, items=None):
        self._items = sorted(items or [])
    
    def add(self, item):
        """Add item and keep sorted."""
        self._items.append(item)
        self._items.sort()
    
    def remove(self, item):
        """Remove item."""
        self._items.remove(item)
    
    # Special methods for list-like behavior
    def __len__(self):
        return len(self._items)
    
    def __getitem__(self, index):
        return self._items[index]
    
    def __setitem__(self, index, value):
        self._items[index] = value
        self._items.sort()  # Re-sort after modification
    
    def __delitem__(self, index):
        del self._items[index]
    
    def __iter__(self):
        return iter(self._items)
    
    def __repr__(self):
        return f"SortedList({self._items})"
    
    def __add__(self, other):
        """Concatenate two SortedLists."""
        if isinstance(other, SortedList):
            return SortedList(self._items + other._items)
        return SortedList(self._items + list(other))
    
    def __contains__(self, item):
        return item in self._items

# Usage
numbers = SortedList([3, 1, 4, 1, 5])
print(numbers)          # SortedList([1, 1, 3, 4, 5])

numbers.add(2)
print(numbers)          # SortedList([1, 1, 2, 3, 4, 5])

print(len(numbers))     # 6
print(3 in numbers)     # True
print(numbers[2])       # 2

more_numbers = SortedList([6, 0])
combined = numbers + more_numbers
print(combined)         # SortedList([0, 1, 1, 2, 3, 4, 5, 6])

Common Special Methods

Method Purpose Example
__init__ Constructor obj = Class()
__str__ String representation print(obj)
__repr__ Developer representation repr(obj)
__len__ Length len(obj)
__getitem__ Indexing obj[key]
__setitem__ Item assignment obj[key] = value
__add__ Addition obj1 + obj2
__eq__ Equality obj1 == obj2
__lt__ Less than obj1 < obj2
__enter__ Context entry with obj:
__exit__ Context exit End of with
__call__ Callable obj()

Key Points to Remember

  • Special methods start and end with __ (called "dunder" methods for double underscore)
  • str for users (readable), repr for developers (unambiguous)
  • Operator overloading with __add__, __sub__, __mul__, etc.
  • Container behavior with __len__, __getitem__, __setitem__, etc.
  • Context managers with __enter__ and __exit__
  • Callable objects with __call__

You've now mastered the fundamentals of Object-Oriented Programming in Python. You understand classes and objects, inheritance, polymorphism, encapsulation, and special methods. OOP is a powerful paradigm that will serve you well in building complex applications. The next intermediate topic will explore Advanced Data Structures and Algorithms - where we'll dive deep into efficient data handling and algorithmic thinking.

© 2026 forEach. All rights reserved.

Privacy Policy•Terms of Service