LearnContact
Lesson 3628 min read

Constructors (__init__)

Go deep on the __init__ constructor method in Python, including default parameters, argument validation, and how it differs from __new__ and __del__.

Introduction

In the previous lesson, you used __init__ briefly to set instance attributes when creating objects. This lesson goes much deeper: you will learn exactly what a constructor is, how to give parameters default values, how to validate arguments before storing them, and how __init__ relates to two other special methods, __new__ and __del__.

What You Will Learn
  • What a constructor is and why __init__ fills that role in Python.
  • How to give __init__ parameters default values.
  • How to validate and process arguments inside the constructor.
  • The difference between __new__ (creates the object) and __init__ (initializes it).
  • A brief introduction to the __del__ destructor.

What is a Constructor?

A constructor is a special method that runs automatically when a new object is created, used to set up that object's initial state. In Python, this role is filled by the __init__ method, which stands for "initialize."

Runs Automatically

__init__ is called the instant an object is created — you never call it directly yourself.

Sets Up State

It is the natural place to assign instance attributes using self.

Accepts Arguments

Values passed when creating an object flow directly into __init__'s parameters.

Can Validate Input

It can check that the values provided make sense before storing them.

The __init__ Method

When you write ClassName(arguments), Python automatically calls __init__ behind the scenes, passing self plus whatever arguments you provided. This is why __init__ always has self as its first parameter, exactly like every other instance method.

init_basic.py
class BankAccount:
    def __init__(self, owner, balance):
        print("Constructor is running...")
        self.owner = owner
        self.balance = balance

account = BankAccount("Priya", 5000)
print(account.owner, account.balance)
Output
Constructor is running...
Priya 5000

Notice that "Constructor is running..." prints before you even see the object's attributes — __init__ genuinely runs the instant BankAccount("Priya", 5000) is evaluated.

Default Parameter Values in __init__

Just like regular functions, __init__ parameters can have default values. This lets you create an object without providing every single argument, falling back to sensible defaults instead.

default_parameters.py
class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.balance = balance

account1 = BankAccount("Priya", 5000)
account2 = BankAccount("Aarav")  # balance defaults to 0

print(account1.owner, account1.balance)
print(account2.owner, account2.balance)
Output
Priya 5000
Aarav 0

account2 was created with only a name, so balance automatically used its default value of 0. Remember from the previous lesson: never use a mutable default like a list directly as a parameter default without care, since defaults are evaluated only once when the function is defined.

Validating and Processing Arguments

Since __init__ is regular Python code, you can add logic to check that the arguments make sense before storing them, raising an exception if something is invalid. This combines directly with what you learned in the exception handling lesson.

validate_arguments.py
class BankAccount:
    def __init__(self, owner, balance=0):
        if not owner:
            raise ValueError("Owner name cannot be empty.")
        if balance < 0:
            raise ValueError("Initial balance cannot be negative.")

        self.owner = owner.strip().title()  # process/clean the argument
        self.balance = balance

account = BankAccount("  priya  ", 1000)
print(account.owner, account.balance)

try:
    bad_account = BankAccount("Aarav", -500)
except ValueError as error:
    print("Error:", error)
Output
Priya 1000
Error: Initial balance cannot be negative.

The constructor both validates balance (rejecting negative numbers) and processes owner (trimming whitespace and standardizing capitalization) before the values are ever stored as attributes.

Why Validate in __init__?

Validating arguments inside the constructor guarantees that once an object exists, its data is already in a valid, consistent state — no other part of your program has to double-check it later.

__new__ vs __init__

Python actually splits object creation into two separate steps, handled by two different special methods. __new__ is responsible for actually creating the new, empty object, while __init__ is responsible for initializing that already-created object with data. You will almost never need to override __new__ yourself as a beginner, but knowing it exists clarifies what __init__ really does.

new_vs_init.py
class Demo:
    def __new__(cls):
        print("__new__ runs first: creates the object")
        instance = super().__new__(cls)
        return instance

    def __init__(self):
        print("__init__ runs second: initializes the object")

obj = Demo()
Output
__new__ runs first: creates the object
__init__ runs second: initializes the object

__new__

  • Creates the raw object in memory
  • Runs before __init__
  • Rarely overridden by beginners

__init__

  • Initializes an already-created object
  • Runs right after __new__
  • The method you will use constantly

The __del__ Destructor (Brief Introduction)

Just as __init__ runs when an object is created, __del__ is a destructor that runs when an object is about to be destroyed — typically when it is no longer referenced anywhere and Python's memory manager cleans it up. It is used far less often than __init__ and its exact timing is not always predictable, so it is only briefly introduced here.

del_method.py
class Connection:
    def __init__(self, name):
        self.name = name
        print(f"Connection '{self.name}' opened.")

    def __del__(self):
        print(f"Connection '{self.name}' closed.")

conn = Connection("Database")
del conn  # explicitly remove the reference
Output
Connection 'Database' opened.
Connection 'Database' closed.
A Word of Caution on __del__

Exactly when __del__ runs is not always guaranteed or immediate, so it should not be relied on for critical cleanup like closing files. For that, prefer the with statement and context managers, which you saw in the File Handling lesson and will study in full detail in the Context Managers lesson later in this course.

Common Mistakes

Avoid These Mistakes
  • Forgetting self as the first parameter of __init__.
  • Using a mutable value (like a list) as a default parameter value in __init__ without care.
  • Storing raw, unvalidated arguments directly onto self without checking them first.
  • Overriding __new__ without a clear reason — it is rarely needed for typical classes.
  • Relying on __del__ for critical cleanup instead of using with and context managers.

Best Practices

  • Use __init__ to set every instance attribute an object needs to function correctly.
  • Give optional parameters sensible default values to make objects easier to create.
  • Validate arguments inside __init__ and raise clear exceptions for invalid input.
  • Leave __new__ alone unless you have a specific, advanced reason to override it.
  • Prefer context managers over __del__ for reliable resource cleanup.

Frequently Asked Questions

Is __init__ the same thing as a constructor in other languages?

Close, but technically __new__ is the true constructor that creates the object, while __init__ initializes it afterward. In everyday Python usage, people commonly call __init__ "the constructor."

Can a class have parameters with default values in __init__?

Yes. Just like regular functions, __init__ parameters can have default values, letting you create objects while omitting some arguments.

What happens if I raise an exception inside __init__?

The object is never fully created — the exception propagates out of the constructor call, so you should wrap object creation in a try/except if invalid input is possible.

Do I need to define __del__ on every class?

No. Most classes never need __del__. It is only useful in specific cases, and even then, context managers are usually a more reliable choice for cleanup.

What comes after constructors in this course?

Next is Encapsulation, the first of the four OOP pillars previewed earlier, where you will learn how to protect an object's internal data.

Key Takeaways

  • __init__ is Python's constructor method, running automatically when an object is created.
  • __init__ parameters can have default values, just like regular function parameters.
  • Validating and processing arguments inside __init__ keeps objects in a consistent, valid state.
  • __new__ creates the raw object; __init__ initializes it — two distinct steps.
  • __del__ is a destructor that runs when an object is cleaned up, but it should not replace context managers for critical cleanup.

Summary

You went deep on __init__, Python's constructor, learning how to set default parameter values and validate arguments before storing them. You also saw how __new__ handles object creation before __init__ runs, and got a brief look at the __del__ destructor.

Lesson 36 Completed
  • You understand __init__ as Python's constructor.
  • You can use default parameter values and validate arguments.
  • You know how __new__ differs from __init__.
  • You are ready to learn Encapsulation, the first pillar of OOP.
Next Lesson →

Encapsulation