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.
class Student:
def __init__(self, name: str, age: int) -> None:
self.name = name
self.age = ageCallers 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.
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)__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.
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())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).
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)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.
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)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.
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)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.
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)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.
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)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.
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)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. @classmethodfactories (see below) that parse input and then callcls(...)with a normal__init__.*args/**kwargsonly 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.
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)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.
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)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.
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)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.
class Parent:
def __init__(self):
print("parent init")
class Child(Parent):
def __init__(self):
print("child init")
Child()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.
class Bad:
def __init__(self):
return 1
try:
Bad()
except TypeError as e:
print(e)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
selffrom__init__(Python raises when the method is called). - Returning a value from
__init__, which triggersTypeError. - 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:
# 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)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.

