Python course menu
Python Constructors: __init__, Default Values, Validation and Dataclasses
Learn Python constructors: how __init__ initialises objects, default values, validating and cleaning arguments, the mutable default trap, alternative constructors with @classmethod, and @dataclass.
On this page
A constructor sets up a new object the moment it is created. In Python, that is the __init__ method. A good constructor gives every object a complete, valid starting state, so the rest of your code can trust it.
The basics#
class Product:
def __init__(self, name, price, stock=0):
self.name = name
self.price = price
self.stock = stock
p = Product("Laptop", 55000, 12)
q = Product("Pen", 20)
print(p.name, p.price, p.stock)
print(q.name, q.stock)Laptop 55000 12
Pen 0When you write Product("Laptop", 55000, 12), Python creates an empty object and immediately calls __init__(that_object, "Laptop", 55000, 12). The arguments follow all the normal parameter rules: required ones first, then defaults.
Missing a required argument is an error, just like with functions:
Product("Laptop")TypeError: Product.__init__() missing 1 required positional argument: 'price'Validate and clean in the constructor#
The constructor is the right place to reject bad data and tidy up input:
class Product:
def __init__(self, name, price):
if price < 0:
raise ValueError("price cannot be negative")
self.name = name.strip().title()
self.price = price
self.created = "2026-10-23" # attributes not passed in
p = Product(" wireless mouse ", 799)
print(p.name, p.price, p.created)
try:
Product("Bad", -5)
except ValueError as e:
print(e)Wireless Mouse 799 2026-10-23
price cannot be negativeBecause the check happens in __init__, an invalid Product can never exist. (In real code, created would usually come from datetime.now().)
Calculated attributes#
Attributes can be derived from the arguments:
class Person:
def __init__(self, name, birth_year):
self.name = name
self.birth_year = birth_year
self.age = 2026 - birth_year
print(Person("Asha", 2005).age)21Values that must stay in sync with other attributes are better computed on demand with a property.
The mutable default trap, again#
The rule from Parameters applies to constructors: never use a list or dictionary as a default.
class Product:
def __init__(self, name, price, tags=None):
self.name = name
self.price = price
self.tags = tags if tags is not None else []
a = Product("Pen", 20)
b = Product("Pencil", 10)
a.tags.append("stationery")
print(a.tags, b.tags)['stationery'] []Each product gets its own list. With tags=[] as the default, both products would share one list.
Alternative constructors with @classmethod#
Sometimes you want to create an object from a different kind of input. A class method receives the class itself as cls and can call it:
class Temperature:
def __init__(self, celsius):
self.celsius = celsius
@classmethod
def from_fahrenheit(cls, f):
return cls((f - 32) * 5 / 9)
t = Temperature.from_fahrenheit(212)
print(t.celsius)100.0You have used this pattern already: datetime.fromisoformat(...) and dict.fromkeys(...) are alternative constructors.
Less typing with @dataclass#
For classes that mainly hold data, the dataclasses module writes __init__, __repr__ and __eq__ for you:
from dataclasses import dataclass, field
@dataclass
class Product:
name: str
price: float
stock: int = 0
tags: list = field(default_factory=list)
p = Product("Laptop", 55000, 12)
print(p)
print(p == Product("Laptop", 55000, 12))Product(name='Laptop', price=55000, stock=12, tags=[])
Truefield(default_factory=list) safely gives each object its own new list. Dataclasses are widely used in modern Python for configuration, records, and API data.
Common mistakes#
Practice#
Practice exercise
Write a Book class whose constructor takes title, author and pages, and sets current_page to 0. Add a read(n) method that advances current_page by n without going past the last page, and a __str__ like Learning Python by A. Writer (300/300). Read 120 pages, then 250 more.
Show hint
self.current_page = min(self.current_page + n, self.pages).
Show solution
class Book:
def __init__(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
self.current_page = 0
def read(self, n):
self.current_page = min(self.current_page + n, self.pages)
return self.current_page
def __str__(self):
return f"{self.title} by {self.author} ({self.current_page}/{self.pages})"
b = Book("Learning Python", "A. Writer", 300)
b.read(120)
b.read(250)
print(b)Learning Python by A. Writer (300/300)min() caps the page at 300 instead of 370.
Key takeaway#
Finished this lesson?