Python __init__(): The Constructor That Sets Up Every Object
Every time you create an object in Python, something runs behind the scenes before you get your hands on it. That something is __init__(). It is not the first thing Python calls — __new__() allocates the memory — but __init__() is where you configure the fresh object with its initial state. For most Python developers, __init__() is effectively “the constructor.”
What __init__() Does
When you write obj = MyClass(args), Python:
- Calls
MyClass.__new__(MyClass)to create the object. - Immediately calls
MyClass.__init__(obj, args)to initialise it. - Returns the object to you.
You rarely interact with step 1 directly. Your job is to write a clear __init__() that gives the object everything it needs to start working correctly.
class Laptop: def __init__(self, brand, ram_gb, storage_gb): self.brand = brand self.ram_gb = ram_gb self.storage_gb = storage_gb self.is_on = False # default state — not powered on yet
laptop = Laptop("Dell", 16, 512)print(laptop.brand) # Dellprint(laptop.is_on) # Falseis_on is not passed in — it is always False when a laptop object is first created. That is a perfectly valid use of __init__(): setting sensible defaults for state you know upfront.
Default Parameters in __init__()
Python’s default parameter syntax works exactly the same in __init__() as in any other function.
class Connection: def __init__(self, host, port=5432, timeout=30): self.host = host self.port = port self.timeout = timeout
# Must pass host; port and timeout are optionaldb = Connection("localhost")print(db.port) # 5432print(db.timeout) # 30
custom = Connection("192.168.1.10", port=3306, timeout=10)print(custom.port) # 3306Default parameters make constructors flexible without requiring callers to specify every detail every time. But remember the mutable default argument trap — never use a list or dict as a default value:
# Bug: all instances share the same listclass Bad: def __init__(self, tags=[]): self.tags = tags
# Fix: use None and create a new list inside __init__class Good: def __init__(self, tags=None): self.tags = tags if tags is not None else []Validation Inside __init__()
__init__() is a good place to reject invalid data early, before the object gets used in ways that could cause confusing errors later.
class Temperature: ABSOLUTE_ZERO = -273.15
def __init__(self, celsius): if celsius < self.ABSOLUTE_ZERO: raise ValueError( f"Temperature {celsius}°C is below absolute zero " f"({self.ABSOLUTE_ZERO}°C)" ) self.celsius = celsius
@property def fahrenheit(self): return (self.celsius * 9 / 5) + 32
t = Temperature(25)print(t.fahrenheit) # 77.0
t_bad = Temperature(-300) # raises ValueError immediatelyFailing fast in __init__() is much easier to debug than allowing an invalid object to exist and cause a mysterious error elsewhere.
Calling the Parent __init__() with super()
When a class inherits from another, its __init__() must usually call the parent’s __init__() to ensure the parent’s setup runs.
class Animal: def __init__(self, name, species): self.name = name self.species = species self.energy = 100
class Dog(Animal): def __init__(self, name, breed): super().__init__(name, species="Canis familiaris") self.breed = breed # Dog-specific attribute
rex = Dog("Rex", "German Shepherd")print(rex.name) # Rex — set by Animal.__init__print(rex.energy) # 100 — also set by Animal.__init__print(rex.breed) # German Shepherd — set by Dog.__init__super().__init__() passes control up the inheritance chain. Without it, rex.name and rex.energy would not exist, and any method from Animal that references them would crash.
What __init__() Should Not Do
__init__() should be quick and not produce side effects that could fail silently.
Do not open files or network connections in __init__() unless you are certain that failure is acceptable during object creation. If the connection fails, the caller gets a confusing error while trying to instantiate a simple object.
# Problematic designclass DatabaseClient: def __init__(self, url): self.connection = connect(url) # what if the DB is down?
# Better: connect explicitlyclass DatabaseClient: def __init__(self, url): self.url = url self.connection = None # not connected yet
def connect(self): self.connection = make_connection(self.url)Do not do heavy computation in __init__() if the object might be created frequently. If initialisation is expensive, consider a factory method that makes the cost explicit.
Do not return a value from __init__(). Python ignores any return value other than None from __init__(), and returning something non-None raises a TypeError.
__init__() vs __new__()
__new__ | __init__ | |
|---|---|---|
| Purpose | Allocates memory, creates the object | Initialises the created object |
| Return value | Must return the new instance | Must return None |
| When to override | Metaclasses, singletons, immutable types | Almost always |
In everyday Python code, you rarely override __new__(). Focus on __init__().
Common Mistakes
Assigning without self. — creates a local variable, not an instance attribute:
class Wrong: def __init__(self, name): name = name # local variable, gone when __init__ returns
class Right: def __init__(self, name): self.name = name # stored on the objectDuplicating parent setup instead of calling super() — if the parent changes its __init__(), your child class breaks silently.
Overloading __init__() (Java style) — Python only accepts one __init__() per class. Use default parameters or @classmethod factory methods for alternative construction patterns.
Previous > Classes and Objects Next > Instance Variables and Methods