Technology  /  Python

🐍 Python 78 guides · updated 2026

From first variable to OOP, generators, and real projects — the language that runs everything from data pipelines to AI agents, taught the practical way.

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:

  1. Calls MyClass.__new__(MyClass) to create the object.
  2. Immediately calls MyClass.__init__(obj, args) to initialise it.
  3. 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) # Dell
print(laptop.is_on) # False

is_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 optional
db = Connection("localhost")
print(db.port) # 5432
print(db.timeout) # 30
custom = Connection("192.168.1.10", port=3306, timeout=10)
print(custom.port) # 3306

Default 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 list
class 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 immediately

Failing 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 design
class DatabaseClient:
def __init__(self, url):
self.connection = connect(url) # what if the DB is down?
# Better: connect explicitly
class 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__
PurposeAllocates memory, creates the objectInitialises the created object
Return valueMust return the new instanceMust return None
When to overrideMetaclasses, singletons, immutable typesAlmost 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 object

Duplicating 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