Constructor in Python: __init__ with Examples

You build an object and want it usable on the very next line, with the customer set, the total filled in and nothing missing. Getting there is exactly what a constructor does.

Quick answer: A constructor in Python is the __init__ method of a class. Python calls it automatically right after it creates a new object, passing the arguments you gave the class call, so you can store them on self:

class Order:
    def __init__(self, customer, total):
        self.customer = customer
        self.total = total

order = Order("Emily Carter", 42.50)   # __init__ runs here

In this tutorial, we’ll build one Order class step by step and use it to cover every common constructor in Python pattern: default and parameterized constructors, default values, keyword-only arguments, *args and **kwargs, type hints and validation.

I ran every example on Python 3.12.5 and captured the key runs in VS Code.

What You Need Before You Start with Python constructors

You need Python 3.10 or newer (the date | None hint in Step 6 uses 3.10 syntax) and a basic idea of classes. If objects are new to you, read how Python handles object-oriented programming first.

Our running sample is an online order for Ridgeline Coffee Roasters in Denver: a customer and a total in US dollars. We’ll change one thing per step.

Step 1: Write a default constructor in Python

A default constructor takes no arguments except self, the new object itself. It sets the same starting values on every object:

class Order:
    def __init__(self):
        self.status = "new"
        self.items = []
        print("__init__ ran for a new Order")

order = Order()
print(order.status, order.items)

Output:

__init__ ran for a new Order
new []

The print() line fires on its own, which proves Python called __init__ for us. If you don’t write any constructor, Python uses an inherited one that does nothing; the empty constructor guide shows when that’s enough.

Step 2: Add a parameterized constructor with required arguments

Most real classes need data from the caller. Add parameters after self and copy each one onto the object. Leave one out and Python refuses to build it:

class Order:
    def __init__(self, customer, city, total):
        self.customer = customer
        self.city = city
        self.total = total

    def summary(self):
        return f"{self.customer} ({self.city}): ${self.total:,.2f}"

first = Order("Emily Carter", "Denver", 42.5)
second = Order("James Walker", "Austin", 118.75)
print(first.summary())
print(second.summary())

try:
    Order("Olivia Brooks", "Seattle")
except TypeError as err:
    print("TypeError:", err)

Output:

Emily Carter (Denver): $42.50
James Walker (Austin): $118.75
TypeError: Order.__init__() missing 1 required positional argument: 'total'
VS Code output of a Python parameterized constructor building two Order objects and a TypeError for a missing argument
Two orders print with their own customer, city and total; the third call fails because total is missing.

Each object keeps its own values because they live on self, not on the class. That’s the difference explained in class vs instance variables in Python, and the __init__ method in Python digs deeper into self.

Step 3: Give constructor parameters default values

Default values make arguments optional. Here shipping defaults to $5.99. The catch is mutable defaults: a list default is created once, when Python reads the def line, so every object shares it:

class BadOrder:
    def __init__(self, customer, items=[]):
        self.customer = customer
        self.items = items              # every order shares one list


class Order:
    def __init__(self, customer, total, shipping=5.99, items=None):
        self.customer = customer
        self.total = total + shipping
        self.items = [] if items is None else list(items)


a, b = BadOrder("Emily Carter"), BadOrder("James Walker")
a.items.append("Colombia 12 oz")
print("BadOrder, James:", b.items)

c = Order("Emily Carter", 42.50)
d = Order("James Walker", 118.75, shipping=0)
c.items.append("Colombia 12 oz")
print("Order, James:   ", d.items)
print(f"Totals: ${c.total:.2f} and ${d.total:.2f}")

Output:

BadOrder, James: ['Colombia 12 oz']
Order, James:    []
Totals: $48.49 and $118.75
VS Code output showing a Python constructor with a list default sharing items between objects, and the None fix keeping them separate
With items=[], James gets Emily’s coffee too. With items=None, his list stays empty.

So use None as the default and create the list inside __init__. The same rule applies to any function, as covered in Python functions with optional arguments.

Step 4: Pass constructor arguments by keyword

Callers can name any argument, in any order. Put a bare * in the signature and every parameter after it must be passed by name, which stops a stray True from landing in the wrong slot:

class Order:
    def __init__(self, customer, total, *, gift=False, note=""):
        self.customer = customer
        self.total = total
        self.gift = gift
        self.note = note

o = Order(total=64.00, customer="Olivia Brooks",
          gift=True, note="Happy birthday!")
print(o.customer, f"${o.total:.2f}", o.gift, o.note)

try:
    Order("Emily Carter", 42.50, True)
except TypeError as err:
    print("TypeError:", err)

Output:

Olivia Brooks $64.00 True Happy birthday!
TypeError: Order.__init__() takes 3 positional arguments but 4 were given

Olivia’s order is built from named arguments in a different order and still comes out right. The positional True is rejected because gift is keyword-only.

Pro Tip: I make flags like gift keyword-only on every class with more than three parameters. Order(..., gift=True) reads far better than a lone True.

Step 5: Accept any number of constructor arguments with *args and **kwargs

When the number of values varies, *items collects extra positional arguments into a tuple and **details collects extra keyword arguments into a dict. The *args vs **kwargs guide covers both in plain functions:

class Order:
    def __init__(self, customer, *items, **details):
        self.customer = customer
        self.items = items          # tuple of (name, price) pairs
        self.details = details      # dict of extra keyword arguments
        self.total = sum(price for _, price in items)

o = Order("James Walker",
          ("Ethiopia Yirgacheffe 12 oz", 18.50),
          ("Ceramic pour-over dripper", 32.00),
          ship_by="2026-11-03", coupon="FALL10")
for name, price in o.items:
    print(f"{name}: ${price:.2f}")
print(o.details)
print(f"{len(o.items)} items, total ${o.total:.2f}")

Output:

Ethiopia Yirgacheffe 12 oz: $18.50
Ceramic pour-over dripper: $32.00
{'ship_by': '2026-11-03', 'coupon': 'FALL10'}
2 items, total $50.50
VS Code output of a Python constructor using *args for order items and **kwargs for shipping details
The two items come from the *items tuple, the shipping details arrive as a dict, and the constructor adds up the total.

James’s order holds 2 items and a total of $50.50. I only use this when the extras are truly open-ended; named parameters document themselves better.

Step 6: Validate arguments and add type hints in the constructor

The constructor is the one place every object passes through, so we check the data there. Type hints document what each parameter expects, and a ValueError rejects bad values before a broken object exists:

from datetime import date


class Order:
    def __init__(self, customer: str, total: float,
                 placed: date | None = None) -> None:
        if total < 0:
            raise ValueError(f"total can't be negative: {total}")
        self.customer = customer.strip().title()
        self.total = round(total, 2)
        self.placed = placed or date(2026, 10, 30)


o = Order("  emily carter ", 42.499, date(2026, 10, 29))
print(o.customer, f"${o.total:.2f}", o.placed)
print(Order("James Walker", 118.75).placed)

try:
    Order("Olivia Brooks", -5)
except ValueError as err:
    print("ValueError:", err)

Output:

Emily Carter $42.50 2026-10-29
2026-10-30
ValueError: total can't be negative: -5
VS Code output of a Python constructor with type hints cleaning a customer name, setting a default date and raising ValueError
The name is cleaned to Emily Carter, 42.499 rounds to $42.50, the second order gets the default date, and a negative total raises ValueError.

Type hints aren’t enforced when the code runs; they’re for editors and checkers like mypy. For rules on a single attribute after creation, the Python property decorator is the next tool.

How do __init__ and __new__ work together in Python?

Strictly speaking, __new__ creates the empty object and __init__ fills it in. When I printed a line from each, __new__ always ran first, then __init__, on every class call.

You’ll almost never write __new__ yourself; it’s for immutable types and singletons. The full comparison is in __new__ vs __init__ in Python.

Python constructor cheat sheet

Every pattern above, tested on the same Order class:

PatternSignatureCall
Default__init__(self)Order()
Parameterized__init__(self, customer, total)Order("Emily Carter", 42.50)
Default valueshipping=5.99, items=NoneOrder("James Walker", 118.75, shipping=0)
Keyword-onlytotal, *, gift=FalseOrder(..., gift=True)
Variable argscustomer, *items, **detailsOrder("James", item1, item2, coupon="FALL10")
Alternative constructor@classmethod from_csv(cls, line)Order.from_csv("Olivia Brooks,64.00")