When you call a class like a function to construct it, Python first creates a new, empty instance, via a separate method, __new__, which you almost never need to override, then immediately calls __init__ on it — self is that newly created instance, and the rest are whatever arguments you passed to the constructor call. __init__ doesn't create the object; it initializes it, typically by assigning arguments to instance attributes through self. It's technically optional — a class with no __init__ just gets a default no-op one — but almost every class that stores data defines one.
1Understanding __init__()
When you call a class like a function to construct it, Python first creates a new, empty instance, via a separate method, __new__, which you almost never need to override, then immediately calls __init__ on it — self is that newly created instance, and the rest are whatever arguments you passed to the constructor call. __init__ doesn't create the object; it initializes it, typically by assigning arguments to instance attributes through self. It's technically optional — a class with no __init__ just gets a default no-op one — but almost every class that stores data defines one.
__init__ should focus on cheap, straightforward setup — assigning attributes, basic validation — and avoid doing expensive work like network calls or file I/O, which surprises callers who just expect object construction to be fast.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(3, 4)
print(p.x, p.y)2Practical Example
Here is a real-world application of __init__() showing how it is used in production Python code.
class BankAccount:
def __init__(self, owner, balance=0):
if balance < 0:
raise ValueError("Initial balance cannot be negative")
self.owner = owner
self.balance = balance
account = BankAccount("Alice", 100)
print(account.balance)3Best Practices
Follow these guidelines when working with __init__():
1. Use __init__ to assign every attribute the instance will need, so an object is never left in a partially-initialized state
2. Validate constructor arguments early inside __init__ and raise a clear exception for invalid input, rather than letting a bad value cause a confusing failure later
3. Call super().__init__(...) first in a subclass's __init__ when the parent class also needs to set up its own state
Tip: __init__ should focus on cheap, straightforward setup — assigning attributes, basic validation — and avoid doing expensive work like network calls or file I/O, which surprises callers who just expect object construction to be fast.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(3, 4)
print(p.x, p.y)