Python magic methods, also known as dunder (double underscore) methods, are special methods that begin and end with double underscores (__
). These methods allow you to define how objects of a class behave in specific situations, such as when using built-in operations or functions. They play a key role in making Python’s classes more powerful and flexible. This tutorial explores the most common magic methods, providing detailed examples and explanations.
Magic methods (or dunder methods) in Python allow you to add "magic" functionality to your classes by defining how they respond to built-in functions and operators. For example, you can define how an object behaves when added with another object, accessed like a dictionary, or printed to the console.
Magic methods provide several benefits:
+
, -
, and *
.len()
, str()
, and repr()
.Magic methods help define how objects are initialized and represented as strings.
__init__
: Object Initialization__init__
is the initializer method that’s called when an object is created. It’s often used to set initial values for instance attributes.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person = Person("Alice", 30)
print(person.name, person.age) # Output: Alice 30
__str__
and __repr__
: Object String Representation__str__
: Defines the “informal” string representation (for users) of an object.__repr__
: Defines the “official” string representation (for developers) of an object.class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"Person: {self.name}, {self.age} years old"
def __repr__(self):
return f"Person(name={self.name}, age={self.age})"
person = Person("Alice", 30)
print(str(person)) # Output: Person: Alice, 30 years old
print(repr(person)) # Output: Person(name=Alice, age=30)
These methods enable you to define custom behavior for arithmetic operators.
__add__
: Addition__add__
allows you to define behavior for the +
operator.
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 __str__(self):
return f"Point({self.x}, {self.y})"
p1 = Point(1, 2)
p2 = Point(3, 4)
print(p1 + p2) # Output: Point(4, 6)
__sub__
: Subtraction__sub__
allows you to define behavior for the -
operator.
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)
p1 = Point(5, 7)
p2 = Point(2, 3)
print(p1 - p2) # Output: Point(3, 4)
__mul__
: Multiplication__mul__
allows you to define behavior for the *
operator.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __mul__(self, scalar):
return Point(self.x * scalar, self.y * scalar)
p = Point(3, 4)
print(p * 2) # Output: Point(6, 8)
These methods allow you to define custom behavior for comparison operators.
__eq__
: Equality__eq__
defines behavior for the ==
operator.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
return self.age == other.age
p1 = Person("Alice", 30)
p2 = Person("Bob", 30)
print(p1 == p2) # Output: True
__lt__
: Less Than__lt__
defines behavior for the <
operator.
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", 25)
p2 = Person("Bob", 30)
print(p1 < p2) # Output: True
__getattr__
and __setattr__
__getattr__
: Called when an attribute is accessed that doesn’t exist.__setattr__
: Used to set an attribute’s value.class Person:
def __init__(self, name):
self.name = name
def __getattr__(self, attribute):
return f"{attribute} attribute not found!"
def __setattr__(self, attribute, value):
print(f"Setting {attribute} to {value}")
super().__setattr__(attribute, value)
p = Person("Alice")
print(p.age) # Output: age attribute not found!
__call__
__call__
allows an instance of a class to be called as if it were a function.
class Greeter:
def __init__(self, name):
self.name = name
def __call__(self):
print(f"Hello, {self.name}!")
greet = Greeter("Alice")
greet() # Output: Hello, Alice!
__len__
: Length of an Object__len__
defines behavior for the len()
function.
class Team:
def __init__(self, members):
self.members = members
def __len__(self):
return len(self.members)
team = Team(["Alice", "Bob", "Charlie"])
print(len(team)) # Output: 3
__getitem__
, __setitem__
, and __delitem__
These methods allow objects to support item access and modification using square brackets.
class CustomList:
def __init__(self, items):
self.items = items
def __getitem__(self, index):
return self.items[index]
def __setitem__(self, index, value):
self.items[index] = value
def __delitem__(self, index):
del self.items[index]
clist = CustomList([10, 20, 30])
print(clist[1]) # Output: 20
clist[1] = 50
print(clist[1]) # Output: 50
del clist[1]
print(clist.items) # Output: [10, 30]
__str__
and __repr__
for Readability: Provide meaningful string representations for your objects to enhance debugging and user experience.__str__
and __repr__
to define human-readable and developer-friendly representations.__add__
and __eq__
to customize operators.__getattr__
and __setattr__
to control access and modification of attributes.__call__
to make objects callable as functions.Magic methods (dunder methods) in Python provide a powerful way to make custom objects behave like built-in types. By implementing methods like __init__
, __str__
, __add__
, and __getitem__
, you can create flexible, intuitive, and robust classes that integrate seamlessly with Python’s syntax and built-in functions. When used thoughtfully, magic methods can enhance readability, flexibility, and functionality in Python code.
With Python’s magic methods, you can:
Ready to integrate magic methods into your classes? Practice implementing them for custom objects to unlock their full potential in Python programming. Happy coding!