Special Methods in Python
Python’s special methods, also known as “magic methods” or “dunder methods” (short for “double underscore”), allow you to define how objects of your classes behave with built-in operations.
1. Initialization and Representation
__init__(self, ...)
: This method is called when a new instance of a class is created. It initializes the object’s attributes.class Person: def __init__(self, name, age): self.name = name self.age = age p = Person("Alice", 30) print(p.name) # Output: Alice
__repr__(self)
: Defines the "official" string representation of an object. It's meant to be unambiguous and is used byrepr()
. It should ideally return a string that could be used to recreate the object.class Person: def __init__(self, name, age): self.name = name self.age = age def __repr__(self): return f"Person(name={self.name!r}, age={self.age!r})" p = Person("Alice", 30) print(repr(p)) # Output: Person(name='Alice', age=30)
__str__(self)
: Defines the "informal" string representation of an object. It’s meant to be readable and is used bystr()
andprint()
.class Person: def __init__(self, name, age): self.name = name self.age = age def __str__(self): return f"{self.name}, age {self.age}" p = Person("Alice", 30) print(p) # Output: Alice, age 30
2. Arithmetic Operations
__add__(self, other)
: Implements addition (+).class Point: def __init__(self, x, y): self.x = x self.y = y def __add__(self, other): return Point(self.x + other.x, self.y + other.y) def __repr__(self): return f"Point({self.x}, {self.y})" p1 = Point(1, 2) p2 = Point(3, 4) print(p1 + p2) # Output: Point(4, 6)
__sub__(self, other)
: Implements subtraction (-).class Point: def __init__(self, x, y): self.x = x self.y = y def __sub__(self, other): return Point(self.x - other.x, self.y - other.y) def __repr__(self): return f"Point({self.x}, {self.y})" p1 = Point(5, 6) p2 = Point(2, 3) print(p1 - p2) # Output: Point(3, 3)
__mul__(self, other)
: Implements multiplication (*).class Vector: def __init__(self, x, y): self.x = x self.y = y def __mul__(self, scalar): return Vector(self.x * scalar, self.y * scalar) def __repr__(self): return f"Vector({self.x}, {self.y})" v = Vector(2, 3) print(v * 3) # Output: Vector(6, 9)
__truediv__(self, other)
: Implements true division (/).class Vector: def __init__(self, x, y): self.x = x self.y = y def __truediv__(self, scalar): return Vector(self.x / scalar, self.y / scalar) def __repr__(self): return f"Vector({self.x}, {self.y})" v = Vector(10, 20) print(v / 2) # Output: Vector(5.0, 10.0)
__floordiv__(self, other)
: Implements floor division (//).class Vector: def __init__(self, x, y): self.x = x self.y = y def __floordiv__(self, scalar): return Vector(self.x // scalar, self.y // scalar) def __repr__(self): return f"Vector({self.x}, {self.y})" v = Vector(10, 20) print(v // 3) # Output: Vector(3, 6)
__mod__(self, other)
: Implements modulo operation (%).class Vector: def __init__(self, x, y): self.x = x self.y = y def __mod__(self, scalar): return Vector(self.x % scalar, self.y % scalar) def __repr__(self): return f"Vector({self.x}, {self.y})" v = Vector(10, 22) print(v % 7) # Output: Vector(3, 1)
__pow__(self, other)
: Implements exponentiation (**).class Number: def __init__(self, value): self.value = value def __pow__(self, exponent): return Number(self.value ** exponent) def __repr__(self): return f"Number({self.value})" n = Number(2) print(n ** 3) # Output: Number(8)
3. Comparison Operations
__eq__(self, other)
: Implements equality comparison (==).class Person: def __init__(self, name, age): self.name = name self.age = age def __eq__(self, other): return (self.name, self.age) == (other.name, other.age) p1 = Person("Alice", 30) p2 = Person("Alice", 30) print(p1 == p2) # Output: True
__ne__(self, other)
: Implements inequality comparison (!=).class Person: def __init__(self, name, age): self.name = name self.age = age def __ne__(self, other): return (self.name, self.age) != (other.name, other.age) p1 = Person("Alice", 30) p2 = Person("Bob", 30) print(p1 != p2) # Output: True
__lt__(self, other)
: Implements less than comparison (<).class Person: def __init__(self, name, age): self.name = name self.age = age def __lt__(self, other): return self.age < other.age p1 = Person("Alice", 30) p2 = Person("Bob", 35) print(p1 < p2) # Output: True
__le__(self, other)
: Implements less than or equal to comparison (<=).class Person: def __init__(self, name, age): self.name = name self.age = age def __le__(self, other): return self.age <= other.age p1 = Person("Alice", 30) p2 = Person("Bob", 30) print(p1 <= p2) # Output: True
__gt__(self, other)
: Implements greater than comparison (>).class Person: def __init__(self, name, age): self.name = name self.age = age def __gt__(self, other): return self.age > other.age p1 = Person("Alice", 30) p2 = Person("Bob", 25) print(p1 > p2) # Output: True
__ge__(self, other)
: Implements greater than or equal to comparison (>=).class Person: def __init__(self, name, age): self.name = name self.age = age def __ge__(self, other): return self.age >= other.age p1 = Person("Alice", 30) p2 = Person("Bob", 30) print(p1 >= p2) # Output: True
4. Attribute Access
__getattr__(self, name)
: Called when trying to access an attribute that doesn’t exist.class DynamicAttributes: def __getattr__(self, name): return f"Attribute {name} not found" obj = DynamicAttributes() print(obj.some_attr) # Output: Attribute some_attr not found
__setattr__(self, name, value)
: Called when setting an attribute’s value. You can use this method to control how attributes are set or to enforce constraints.class SafeDict: def __init__(self): self._data = {} def __setattr__(self, name, value): if name.startswith('_'): super().__setattr__(name, value) else: self._data[name] = value def __repr__(self): return repr(self._data) obj = SafeDict() obj.key = "value" print(obj) # Output: {'key': 'value'}
__delattr__(self, name)
: Called when deleting an attribute.class SafeDict: def __init__(self): self._data = {} def __delattr__(self, name): if name in self._data: del self._data[name] else: super().__delattr__(name) def __repr__(self): return repr(self._data) obj = SafeDict() obj.key = "value" del obj.key print(obj) # Output: {}
5. Container and Sequence Operations
__getitem__(self, key)
: Called to retrieve an item using theobj[key]
syntax.class MyContainer: def __init__(self): self.data = [1, 2, 3, 4, 5] def __getitem__(self, index): return self.data[index] container = MyContainer() print(container[2]) # Output: 3
__setitem__(self, key, value)
: Called to set an item using theobj[key] = value
syntax.class MyContainer: def __init__(self): self.data = [1, 2, 3, 4, 5] def __setitem__(self, index, value): self.data[index] = value container = MyContainer() container[2] = 10 print(container.data) # Output: [1, 2, 10, 4, 5]
__delitem__(self, key)
: Called to delete an item using thedel obj[key]
syntax.class MyContainer: def __init__(self): self.data = [1, 2, 3, 4, 5] def __delitem__(self, index): del self.data[index] container = MyContainer() del container[2] print(container.data) # Output: [1, 2, 4, 5]
__len__(self)
: Called to return the length of an object usinglen(obj)
.class MyContainer: def __init__(self, items): self.items = items def __len__(self): return len(self.items) container = MyContainer([1, 2, 3]) print(len(container)) # Output: 3
__contains__(self, item)
: Called to check for membership using theitem in obj
syntax.class MyContainer: def __init__(self, items): self.items = items def __contains__(self, item): return item in self.items container = MyContainer([1, 2, 3]) print(2 in container) # Output: True
__iter__(self)
: Called to return an iterator object.__next__(self)
defines the behavior of an iterator.class Countdown: def __init__(self, start): self.start = start def __iter__(self): return self def __next__(self): if self.start <= 0: raise StopIteration current = self.start self.start -= 1 return current # Usage countdown = Countdown(5) for number in countdown: print(number) # Output: # 5 # 4 # 3 # 2 # 1
6. Callable Objects
__call__(self, ...)
: Allows an instance of a class to be called as a function.class Adder: def __init__(self, increment): self.increment = increment def __call__(self, value): return value + self.increment add_five = Adder(5) print(add_five(10)) # Output: 15
7. Context Management
__enter__(self)
: Defines the setup code for a context manager. This method is called when entering the context of thewith
statement.class Resource: def __enter__(self): print("Resource acquired") return self def __exit__(self, exc_type, exc_value, traceback): print("Resource released") with Resource() as res: print("Inside with block") # Output: # Resource acquired # Inside with block # Resource released
__exit__(self, exc_type, exc_value, traceback)
: Defines the cleanup code for a context manager. This method is called when exiting the context of thewith
statement.class Resource: def __enter__(self): print("Resource acquired") return self def __exit__(self, exc_type, exc_value, traceback): print("Resource released") # Optionally suppress exceptions return False with Resource() as res: print("Inside with block") # Output: # Resource acquired # Inside with block # Resource released
8. Descriptors
__get__(self, instance, owner)
: Defines behavior for accessing a value. Used in descriptor objects.class Descriptor: def __get__(self, instance, owner): return "Descriptor value" class MyClass: attr = Descriptor() obj = MyClass() print(obj.attr) # Output: Descriptor value
__set__(self, instance, value)
: Defines behavior for setting a value. Used in descriptor objects.class Descriptor: def __set__(self, instance, value): print(f"Setting value: {value}") class MyClass: attr = Descriptor() obj = MyClass() obj.attr = 42 # Output: Setting value: 42
__delete__(self, instance)
: Defines behavior for deleting a value. Used in descriptor objects.class Descriptor: def __delete__(self, instance): print("Deleting value") class MyClass: attr = Descriptor() obj = MyClass() del obj.attr # Output: Deleting value
9. Object Lifecycle
__new__(cls, ...)
: Called to create a new instance of a class. It’s a static method used to control the creation of a new instance before__init__
is called.class Singleton: _instance = None def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance a = Singleton() b = Singleton() print(a is b) # Output: True
__del__(self)
: Called when an object is about to be destroyed, i.e., its destructor. This method is used for cleanup before the object is garbage collected.class Resource: def __init__(self): print("Resource acquired") def __del__(self): print("Resource released") obj = Resource() del obj # Output: # Resource acquired # Resource released
Last updated
Was this helpful?