Skip to content
Laviyx Learn

Try

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.

Intermediate8 min readUpdated
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#

Python
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)
Output
Laptop 55000 12
Pen 0

When 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:

Python
Product("Laptop")
Output
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:

Python
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)
Output
Wireless Mouse 799 2026-10-23
price cannot be negative

Because 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:

Python
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)
Output
21

Values 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.

Python
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)
Output
['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:

Python
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)
Output
100.0

You 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:

Python
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))
Output
Product(name='Laptop', price=55000, stock=12, tags=[])
True

field(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
Python
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)
Output
Learning Python by A. Writer (300/300)

min() caps the page at 300 instead of 370.

Key takeaway#

Finished this lesson?