In Python, the __init__ method is one of the most fundamental special methods in the language.
Called automatically when an object is created, it allows you to initialize the attributes of a class instance. If you want to master object-oriented programming in Python, understanding __init__ is absolutely essential.
Whether you are a beginner or an experienced developer, our comprehensive Python course guides you step by step through learning these essential concepts.
Definition of __init__ in Python
The __init__ method is a special method (also called a dunder method, for "double underscore") that serves as a constructor in Python. Its role is to initialize a new object right after its creation in memory.
Concretely, when you create an instance of a class with MyClass(), Python performs two operations behind the scenes:
__new__: creates the object in memory (rarely overridden).__init__: initializes the object with the values you pass to it.
Technically, __init__ is not a "constructor" in the strict sense (it is __new__ that creates the object), but it is called so by convention because it is the one that configures the object for use.
The basic syntax is as follows:
class MyClass:
def __init__(self):
# Initialize attributes
self.attribute = valueThe first parameter of __init__ is always self, which represents the instance being created. You can then add as many additional parameters as needed.
Syntax and parameters of __init__
The __init__ method is defined inside a class using the def keyword. Here is its complete syntax with different types of parameters:
__init__ with simple parameters
class User:
def __init__(self, name, email, age):
self.name = name
self.email = email
self.age = age
# Creating an instance
user = User("Alice", "alice@example.com", 30)
print(user.name) # Alice
print(user.email) # alice@example.com
print(user.age) # 30In this example, name, email and age are required parameters. If you try to create a User without providing them, Python will raise a TypeError.
__init__ with default values
You can define default values for certain parameters, making them optional:
class User:
def __init__(self, name, email, age=18, role="member"):
self.name = name
self.email = email
self.age = age
self.role = role
# With default values
user1 = User("Bob", "bob@example.com")
print(user1.age) # 18
print(user1.role) # member
# Overriding default values
user2 = User("Charlie", "charlie@example.com", age=25, role="admin")
print(user2.age) # 25
print(user2.role) # admin
__init__ with *args and **kwargs
For maximum flexibility, you can use *args and **kwargs:
class ServerConfig:
def __init__(self, host, port, **options):
self.host = host
self.port = port
self.options = options
server = ServerConfig("localhost", 8080, debug=True, timeout=30)
print(server.options) # {'debug': True, 'timeout': 30}
Practical examples and use cases
Let us now look at concrete and progressive examples to understand how __init__ is used in real-world situations.
Initialization with data validation
A very common use of __init__ is to validate data as soon as the object is created:
class BankAccount:
def __init__(self, holder, initial_balance=0):
if not isinstance(holder, str) or len(holder) == 0:
raise ValueError("The holder must be a non-empty string.")
if initial_balance < 0:
raise ValueError("The initial balance cannot be negative.")
self.holder = holder
self.balance = initial_balance
self.history = []
def deposit(self, amount):
self.balance += amount
self.history.append(f"Deposit: +${amount}")
def withdraw(self, amount):
if amount > self.balance:
raise ValueError("Insufficient balance.")
self.balance -= amount
self.history.append(f"Withdrawal: -${amount}")
# Usage
account = BankAccount("Alice", 1000)
account.deposit(500)
account.withdraw(200)
print(account.balance) # 1300
print(account.history) # ['Deposit: +$500', 'Withdrawal: -$200']Warning: never modify attributes directly that should be protected. Use properties (@property) to control access if needed.
Initialization with computed attributes
The __init__ method can also compute attributes from the received parameters:
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
self.area = width * height
self.perimeter = 2 * (width + height)
def __repr__(self):
return f"Rectangle({self.width}, {self.height})"
rect = Rectangle(10, 5)
print(rect.area) # 50
print(rect.perimeter) # 30
print(rect) # Rectangle(10, 5)
Inheritance and calling the parent __init__ with super()
When you create a class that inherits from another, it is essential to call the parent's __init__ so that the object is correctly initialized:
class Animal:
def __init__(self, name, species):
self.name = name
self.species = species
self.alive = True
class Dog(Animal):
def __init__(self, name, breed, age):
super().__init__(name, species="Dog")
self.breed = breed
self.age = age
def bark(self):
return f"{self.name} says Woof!"
class Cat(Animal):
def __init__(self, name, color):
super().__init__(name, species="Cat")
self.color = color
def meow(self):
return f"{self.name} says Meow!"
rex = Dog("Rex", "German Shepherd", 5)
print(rex.name) # Rex
print(rex.species) # Dog
print(rex.breed) # German Shepherd
print(rex.bark()) # Rex says Woof!
felix = Cat("Felix", "black")
print(felix.meow()) # Felix says Meow!The super() function allows you to call methods from the parent class. This is the recommended way to handle inheritance in Python, rather than calling Animal.__init__(self, ...) directly.
Comparison with dataclasses
Python offers dataclass which automatically generate the __init__ method for you. Let's see the difference:
from dataclasses import dataclass
# Without dataclass: manual __init__
class ClassicProduct:
def __init__(self, name, price, stock=0):
self.name = name
self.price = price
self.stock = stock
# With dataclass: automatically generated __init__
@dataclass
class DataclassProduct:
name: str
price: float
stock: int = 0
# Both work the same way
p1 = ClassicProduct("Keyboard", 49.99, 10)
p2 = DataclassProduct("Keyboard", 49.99, 10)
print(p1.name, p1.price) # Keyboard 49.99
print(p2.name, p2.price) # Keyboard 49.99| Criteria | Manual __init__ | @dataclass |
|---|---|---|
| Flexibility | Full (custom logic) | Limited (via __post_init__) |
| Verbosity | More code to write | Concise and readable |
| Auto-generated methods | None | __repr__, __eq__, etc. |
| Ideal use case | Complex initialization logic | Simple data objects |
Best practices for __init__
Here are the essential rules for writing good __init__ methods in Python:
1. Keep __init__ simple and focused
The role of __init__ is to initialize the object, not to perform complex operations. If your constructor is more than 15-20 lines long, it is probably a sign that you need to refactor:
# ❌ Bad practice: too much logic in __init__
class SalesReport:
def __init__(self, file):
self.file = file
self.data = self._load_file(file) # heavy operation
self.total = sum(d['amount'] for d in self.data)
self.average = self.total / len(self.data)
self._send_notification() # side effect
# ✅ Good practice: simple __init__, separate methods
class SalesReport:
def __init__(self, file):
self.file = file
self.data = []
self.total = 0
self.average = 0
def load(self):
self.data = self._load_file(self.file)
self.total = sum(d['amount'] for d in self.data)
self.average = self.total / len(self.data)
return self
2. Initialize all attributes in __init__
All instance attributes should be defined in __init__, even if they are initialized to None. This makes your code more readable and facilitates the docstring:
class Connection:
def __init__(self, url, timeout=30):
self.url = url
self.timeout = timeout
self.session = None # Will be initialized later
self.is_connected = False # Explicit initial state
self.last_error = None
3. Avoid mutable objects as default values
This is one of the most classic pitfalls in Python:
# ❌ DANGER: the list is shared between all instances!
class Team:
def __init__(self, name, members=[]):
self.name = name
self.members = members
t1 = Team("Alpha")
t1.members.append("Alice")
t2 = Team("Beta")
print(t2.members) # ['Alice'] — Surprise!
# ✅ Good practice: use None and create a new list
class Team:
def __init__(self, name, members=None):
self.name = name
self.members = members if members is not None else []Mutable objects (list, dict, set) used as default values are shared between all instances. Always use None as the default value, then create a new object in the body of __init__.
4. Use type annotations
Type annotations improve readability and allow static analysis tools to check your code:
from typing import Optional
class Article:
def __init__(self, title: str, content: str, author: str, tags: Optional[list[str]] = None) -> None:
self.title = title
self.content = content
self.author = author
self.tags = tags if tags is not None else []
5. Use alternative class methods
If you need multiple ways to create an object, use class methods with @classmethod rather than overloading __init__:
import json
class Configuration:
def __init__(self, host: str, port: int, debug: bool = False):
self.host = host
self.port = port
self.debug = debug
@classmethod
def from_json(cls, file_path: str):
with open(file_path) as f:
data = json.load(f)
return cls(data['host'], data['port'], data.get('debug', False))
@classmethod
def default(cls):
return cls('localhost', 8080, debug=True)
# Different ways to create the object
config1 = Configuration("192.168.1.1", 3000)
config2 = Configuration.from_json("config.json")
config3 = Configuration.default()
__init__ and related special methods
The __init__ method is part of a larger ecosystem of special methods in Python. Here are the most important ones to know in relation to initialization:
| Method | Role | When is it called? |
|---|---|---|
__new__ | Creates the instance in memory | Before __init__ |
__init__ | Initializes the instance | Right after __new__ |
__post_init__ | Post-initialization (dataclasses) | After auto-generated __init__ |
__del__ | Cleanup before destruction | When the object is destroyed |
__repr__ | Developer representation | repr(object) |
__str__ | User representation | str(object) / print |
Here is an example combining __init__ with other special methods:
class Vector:
def __init__(self, x: float, y: float):
self.x = x
self.y = y
def __repr__(self):
return f"Vector({self.x}, {self.y})"
def __str__(self):
return f"({self.x}, {self.y})"
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __len__(self):
return int((self.x ** 2 + self.y ** 2) ** 0.5)
v1 = Vector(3, 4)
v2 = Vector(1, 2)
v3 = v1 + v2
print(v3) # (4, 6)
print(repr(v3)) # Vector(4, 6)
print(v1 == v2) # False
Frequently asked questions
What is the difference between __init__ and __new__ in Python?
__new__ is responsible for creating the instance in memory. It receives the class (cls) as its first parameter and returns a new object. __init__ is then called to initialize that object with attributes. In 99% of cases, you only need __init__. __new__ is only useful in advanced cases such as singletons or immutable classes (for example, to subclass tuple or str).
Why must self always be the first parameter of __init__?
self is a Python convention that refers to the instance being created. When you write object = MyClass(args), Python internally calls MyClass.__init__(object, args). The self parameter therefore automatically receives the reference to the object. Although you could technically use a different name, self is a universal convention in Python and not following it would make your code very difficult to read.
Can __init__ return a value?
No. The __init__ method must never return anything other than None. If you try to return a value, Python will raise a TypeError: __init__() should return None error. Its role is solely to initialize the object, not to create or return anything. If you need to control what is returned during creation, it is __new__ that you need to override.
How can I learn to master __init__ and OOP in Python?
To properly understand __init__ and all of object-oriented programming in Python, nothing beats a structured and progressive approach. Our dedicated Python course on Believemy guides you from discovering the basics to advanced concepts like inheritance, design patterns and special methods. You will find practical exercises and real-world projects to consolidate your skills.