Python Constructor: __init__ Method Explained

Learn Python constructors using the __init__ method with examples for self, parameters, default values, validation, copying mutable arguments, object creation, inheritance, dataclasses, __new__, and common constructor mistakes.

Published

Updated

Read time 10 min read

Reviewed byDeepak Prasad

Python Constructor: __init__ Method Explained

This article is for readers learning how classes are constructed in Python—especially __init__, self, parameters and defaults, validation, copying mutable inputs, inheritance with super(), type hints, a short comparison with __new__, and how dataclasses relate to generated constructors.

If you are new to classes altogether, the Python class example page walks through objects and methods before constructors in depth.

Tested on: Python 3.13.3; kernel 6.14.0-37-generic.


What is a constructor in Python?

Python does not use a constructor keyword. When you write Student("Amit", 20), the interpreter creates an instance of Student, then runs that class’s __init__ method (if defined) to attach starting state. The Python tutorial on classes describes classes as templates for objects with attributes and methods; the data model documents that __init__ is called after the instance exists and must return None.

So in everyday speech, the constructor is __init__: the place you assign self.name, self.age, and other instance attributes so each object starts in a valid shape.


Python constructor syntax using __init__()

__init__ is an instance method defined on the class. Its first parameter must refer to the instance; by convention that name is self. Additional parameters list the values callers pass when they construct the object.

python
class Student:
    def __init__(self, name: str, age: int) -> None:
        self.name = name
        self.age = age
Output

Callers invoke Student("Amit", 20); Python creates the object, passes it as self, and passes "Amit" and 20 as name and age. The -> None return annotation matches the rule that __init__ must not return a value; parameter annotations document intent for readers and type checkers.


Simple Python constructor example

Here is the same pattern end-to-end: define __init__, store arguments on self, then create an object.

python
class Student:
    def __init__(self, name: str, age: int) -> None:
        self.name = name
        self.age = age


student1 = Student("Amit", 20)
print(student1.name, student1.age)
Output

__init__ runs automatically for each Student(...). self is the instance being built, so self.name and self.age become attributes you read later as student1.name and student1.age. The names name and age are only parameters; the attributes live on the object under those names because you assign them on self.


How __init__ works during object creation

Construction has two conceptual steps: allocate the instance, then initialize it. For normal classes you only write __init__; Python handles allocation. Arguments you pass in the call are forwarded to __init__ (along with the new instance as self) so you can finish setup before the caller uses the object.

If __init__ raises an exception, the instance may still exist briefly in a partially initialized state in some versions; for tutorials, treat failed initialization as a signal to fix inputs or validation rather than rely on the object.


What is self in a constructor?

self is not a reserved word; it is the agreed name for the first parameter of instance methods. When you call student1.greet(), Python passes student1 as the first argument to greet. The same happens for __init__: the freshly created object is self, and you attach data to it with self.field = value.

Every other instance method uses the same rule: the object left of the dot becomes self inside the method body.

python
class Student:
    def __init__(self, name: str, age: int) -> None:
        self.name = name
        self.age = age

    def summary(self) -> str:
        return f"{self.name}, age {self.age}"


s = Student("Amit", 20)
print(s.summary())
Output

summary reads self.name and self.age that __init__ stored earlier on the same instance.


Parameterized constructor in Python

A parameterized constructor is simply __init__ with one or more parameters after self. They define what the caller must supply (unless you also add defaults).

python
class Car:
    def __init__(self, make: str, model: str) -> None:
        self.make = make
        self.model = model


my_car = Car("Toyota", "Camry")
print(my_car.make, my_car.model)
Output

Annotations on __init__ parameters are optional but help document the contract; -> None reminds readers that the constructor must not return a value.

This mirrors ordinary Python functions: the parameter list after self is yours to design.


Constructor with default values

Default parameter values work the same as in any function: omitted arguments use the default.

python
class Dog:
    def __init__(self, name: str, breed: str = "Unknown") -> None:
        self.name = name
        self.breed = breed


print(Dog("Fido", "Labrador").breed)
print(Dog("Stray").breed)
Output

The second call omits breed, so it becomes "Unknown".


Validate arguments in __init__

Constructors are a good place to reject impossible state before anyone uses the object. Raise a built-in or custom exception instead of returning an error code from __init__ (which cannot return anything useful anyway). The surrounding try / except flow is where callers usually handle those failures.

python
class Student:
    def __init__(self, name: str, age: int) -> None:
        if age < 0:
            raise ValueError("age must be non-negative")
        self.name = name
        self.age = age


Student("Amit", 20)
try:
    Student("Test", -1)
except ValueError as e:
    print(e)
Output

Copy mutable inputs in __init__

Two related issues: a mutable default argument (already covered below), and a caller-owned list you store by reference. If you assign self.items = items when items is a list, later in-place changes to that same list object from outside the class mutate your instance. Taking a shallow copy with list(items) (or items.copy()) keeps the object’s list independent for one level of nesting.

python
class Cart:
    def __init__(self, items: list | None = None) -> None:
        self.items = [] if items is None else list(items)


shared = [1, 2]
c = Cart(shared)
shared.append(99)
print(c.items)
Output

You should still see [1, 2] because Cart copied the sequence at construction time.


Create multiple objects using a constructor

Each call to the class runs __init__ again and produces a distinct instance with its own attributes.

python
class Student:
    def __init__(self, name: str, age: int) -> None:
        self.name = name
        self.age = age


a = Student("Amit", 20)
b = Student("Priya", 19)
print(a is b)
print(a.name, b.name)
Output

You should see False for a is b because they are two different objects.


Can Python have multiple constructors?

Python does not support multiple __init__ methods in the same class. If you define __init__ more than once, the last definition replaces the earlier one, so only the final signature exists.

python
class Demo:
    def __init__(self) -> None:
        self.flag = "no-arg"

    def __init__(self, value: int) -> None:
        self.flag = value


x = Demo(1)
print(x.flag)
Output

Trying Demo() without arguments would fail here because the surviving __init__ requires value.

Common patterns instead of overloading:

  • Default parameters so one __init__ covers several call shapes.
  • @classmethod factories (see below) that parse input and then call cls(...) with a normal __init__.
  • *args / **kwargs only when you truly need a wide contract; keep validation clear so callers know what to pass.

Alternative constructors using @classmethod

A class method takes the class as its first argument (usually cls). It can construct an instance by calling cls(...), which still runs __init__. That pattern is the usual Python answer to “another way to build this object.” See the classmethod guide for more detail on cls and factories.

python
class Customer:
    def __init__(self, first_name: str, last_name: str) -> None:
        self.first_name = first_name
        self.last_name = last_name

    @classmethod
    def from_full_name(cls, full_name: str) -> "Customer":
        first, last = full_name.split(" ", 1)
        return cls(first, last)


p = Customer.from_full_name("Jane Doe")
print(p.first_name, p.last_name)
Output

Constructor in inheritance

Subclasses often define their own __init__. If the parent also defines __init__, you choose whether to reuse it with super() or replace its behavior entirely.

Call parent constructor using super()

If the child adds fields but the parent still has initialization work, call super().__init__(...) so both layers run.

python
class Parent:
    def __init__(self):
        self.parent_id = 1


class Child(Parent):
    def __init__(self):
        super().__init__()
        self.child_id = 2


c = Child()
print(c.parent_id, c.child_id)
Output

When both base and subclass constructors take arguments, pass the parent’s parameters through super().__init__(...), then set fields that exist only on the child.

python
class Person:
    def __init__(self, name: str) -> None:
        self.name = name


class Employee(Person):
    def __init__(self, name: str, employee_id: int) -> None:
        super().__init__(name)
        self.employee_id = employee_id


e = Employee("Amit", 1001)
print(e.name, e.employee_id)
Output

More examples and ordering rules live in the Python super() article.

Override parent constructor in child class

If the child defines __init__ and never calls super().__init__, the parent’s __init__ does not run unless Python falls back to a parent implementation because the child has no __init__ at all.

python
class Parent:
    def __init__(self):
        print("parent init")


class Child(Parent):
    def __init__(self):
        print("child init")


Child()
Output

Only child init prints here. Add super().__init__() when you still need the parent side effects.


Dataclasses and generated __init__

For data-heavy classes, the @dataclass decorator can generate an __init__ (and other boilerplate) from annotated fields so you do not write the same assignments by hand. You can still add __post_init__ for derived fields or checks after the generated constructor runs. When that style fits your model, see the Python dataclasses guide on this site.


__init__ vs __new__ in Python

__new__ __init__
Purpose Creates (or selects) the instance Initializes the instance after it exists
First parameter cls self
Typical use Rare; customizing creation for immutables or advanced cases Almost all everyday constructors
Return value Must return an instance (often super().__new__(cls)) for normal classes Must return None

The data model notes that __new__ is mainly for customizing instance creation, including subclasses of immutable types such as int, str, and tuple, and that __init__ is not called if __new__ does not return an instance of cls. For most application classes, you only implement __init__.


Can a constructor return a value?

__init__ must not return a non-None value. It initializes an object that already exists; the caller already receives the instance from the class call. Returning anything else raises TypeError.

python
class Bad:
    def __init__(self):
        return 1


try:
    Bad()
except TypeError as e:
    print(e)
Output

If you need validation that rejects construction, raise an exception from __init__ instead of returning a sentinel. If you need a different object type, that belongs in a factory function or __new__, not a return from __init__.


Common mistakes with Python constructors

  • Omitting self from __init__ (Python raises when the method is called).
  • Returning a value from __init__, which triggers TypeError.
  • Defining two __init__ methods and expecting overload behavior like Java or C++.
  • Using a mutable default such as [] or {}, which is shared across instances unless you build a fresh value inside __init__.
  • Storing a caller-owned list (or other mutable) by reference when you meant to snapshot it; copy with list(...) or similar (see the section on copying mutable inputs).
  • Forgetting super().__init__(...) when the parent still must run setup.
  • Putting large business workflows inside __init__; keep it to sane field setup and light validation so objects stay easy to test.
  • Confusing __init__ with __new__ and trying to “return” constructed objects from __init__.

Mutable default example:

python
# Avoid
class Cart:
    def __init__(self, items=[]):
        self.items = items


# Better
class Cart:
    def __init__(self, items=None):
        self.items = [] if items is None else list(items)
Output

Python constructor quick reference table

Need Use
Define a constructor def __init__(self):
Add parameters def __init__(self, name):
Store object data self.name = name
Default value def __init__(self, name="Guest"):
Type hints (optional) def __init__(self, name: str) -> None:
Validate inputs if not ok: raise ValueError(...) inside __init__
Copy a list argument self.items = list(items)
Parent constructor super().__init__(...)
Alternative constructor @classmethod + return cls(...)
Data-only boilerplate @dataclass (generated __init__)
Control object creation Override __new__ (advanced)
Return value from __init__ Do not return anything (only None)

Summary

In Python, the constructor people write day to day is __init__: it receives the new instance as self, takes the parameters your API needs, and assigns instance attributes. Use type hints and -> None when they help readers and tools, validate with exceptions instead of return codes, and copy mutable inputs when callers should not share live references with your object. Defaults and *args/**kwargs cover many call patterns; only one __init__ may exist per class, so alternative construction paths use @classmethod helpers that call cls(...). In subclasses, call super().__init__(...) when parent initialization still matters—including when both sides take constructor arguments. For mostly declarative data classes, @dataclass can generate __init__ for you. __new__ allocates the object and is rarely customized except for advanced cases described in the data model. Keep __init__ free of return values and be careful with mutable defaults so each instance owns its own lists and dicts.


References


Frequently Asked Questions

1. What is a constructor in Python?

Python uses the instance method init to initialize a new object after it is created; there is no separate keyword like in some languages, and init is what people usually mean by the constructor.

2. What is self in __init__?

self is the conventional name for the first parameter of an instance method; when you call ClassName(...), Python passes the new instance as self so init can attach attributes like self.name.

3. Can Python have multiple __init__ methods in one class?

No. If you define init twice, the second definition replaces the first. Use default parameters, *args/**kwargs with care, or @classmethod alternative constructors instead.

4. Can __init__ return a value in Python?

No. init must return None; returning any other value raises TypeError because init only initializes an object that already exists—it does not replace object construction.

5. How do you call the parent constructor in a subclass?

Inside the child init, call super().init(...) with the arguments the parent expects, before or after setting child-specific attributes depending on your design.

6. Does every Python class need __init__?

No. If you omit init, Python uses the default from object that accepts no extra arguments beyond self; add init when you need custom parameters or attribute setup.

7. Should you validate arguments in __init__?

Yes when invalid state would be dangerous; raise ValueError or a more specific exception so construction fails before the caller uses a bad object, instead of returning a value from init.
Bashir Alam

Data Analyst and Machine Learning Engineer

Computer Science graduate from the University of Central Asia, currently employed as a full-time Machine Learning Engineer at uExel. His expertise lies in OCR, text extraction, data preprocessing, and …