Updated May 2026

Introduction

When I first wrote a Python class for a small command-line budget tracker, I copied __init__ from a tutorial without understanding what it actually did. The double underscores looked strange. self felt like an extra argument I did not ask for. And the whole thing ran without me ever calling it directly. It worked, but I could not explain why.

__init__ is the method Python calls automatically when you create an object from a class. Its job is to initialize that object’s starting state. It sets the attributes and values the object needs from the moment it exists. Many people call it a Python constructor. That is close enough for conversation. Technically, it is an initializer. Python creates the object first. Then it hands the object to __init__ to set up.

By the end of this article, you will understand what __init__ does in Python. You will also learn how self works, how to use default values, and how to avoid common beginner mistakes.

What __init__ does in Python

__init__ is the method Python runs when an object is instantiated so the object starts with the attributes and values it needs.

In Python, __init__ is a special method, sometimes called a “dunder method” because of the double underscores on each side. (“Dunder” is short for “double underscore.”) You do not call __init__ yourself. Python calls it automatically after a new instance is created from a class.

The __init__ method lets the class initialize the object’s attributes. It assigns them their first values. It does not create the object itself.

Here is a simple example:

class UserAccount:
    def __init__(self, username, email):
        self.username = username
        self.email = email

When you write UserAccount("sam", "sam@example.com"), Python creates the object, then calls __init__ to store "sam" and "sam@example.com" as attributes on that specific instance.

Why __init__ feels confusing at first

The confusion around __init__ in Python is real. Most of it comes from a few specific things stacking up at once.

The double underscores look unusual if you have not seen them before. The method runs automatically, so you never see it being called explicitly. self appears as the first parameter, but the caller never passes it in. If you have worked with Java or C++, calling __init__ a “constructor” creates expectations that do not quite match what Python is doing.

You normally do not call my_object.__init__(...) yourself. You call the class, and Python handles the rest.

I think most __init__ confusion comes from the way OOP is taught, not from the method itself. Tutorials often introduce abstract class hierarchies before showing you a single useful example. Once you see __init__ in the context of real code, the purpose of each part becomes clear.

Classes, objects, and self without the jargon

You need a small amount of class-and-object context to understand __init__. Only a small amount.

A class is a template. Think of it like a blank form. It defines which fields exist. It does not contain any specific data. An object is a filled-out version of that form. Each object holds its own values.

self refers to the specific object being created or used. Inside __init__, writing self.attribute = value stores data on that particular instance. It does not store data on the class as a whole.

class UserAccount:
    def __init__(self, username, email):
        self.username = username
        self.email = email

account_1 = UserAccount("sam", "sam@example.com")
account_2 = UserAccount("lee", "lee@example.com")

print(account_1.username)  # sam
print(account_2.username)  # lee

account_1 and account_2 are both UserAccount objects. They were created from the same class. Each one holds different data. That is because self pointed to a different instance each time __init__ ran.

Here is what matters about self:

  • It appears as the first parameter in the method definition.
  • You do not type it when you call the class. Python passes the instance automatically.
  • Every time you write self.something = value inside __init__, you are attaching data to that one object.

Once you see self as “the object I am currently setting up,” the syntax makes sense.

How __init__ works step by step

When you write a line like this, Python follows these steps:

account = UserAccount("sam", "sam@example.com")

  1. Python sees the class call UserAccount("sam", "sam@example.com").
  2. Python creates a new, empty instance of UserAccount.
  3. Python passes that instance into __init__ as self.
  4. Python matches the remaining arguments to the method’s parameters: "sam" goes to username, "sam@example.com" goes to email.
  5. Inside __init__, self.username = username and self.email = email attach those values to the instance.
  6. The finished object is returned and assigned to account.

__init__ always returns None. Its job is setup. It does not produce a new or different object. If you try to return something else from __init__, Python raises a TypeError.

Think of this sequence like a debugger walkthrough. When something goes wrong during object creation, retracing these steps usually points you to the problem.

Basic __init__ example with required parameters

The most common pattern is an __init__ that takes required parameters and stores them as instance attributes.

class Product:
    def __init__(self, name, price, in_stock):
        self.name = name
        self.price = price
        self.in_stock = in_stock

item = Product("Mechanical Keyboard", 89.99, True)

print(item.name)       # Mechanical Keyboard
print(item.price)      # 89.99
print(item.in_stock)   # True

The values passed into __init__ become attributes on the object. You access them later with dot notation: item.name, item.price, item.in_stock.

Every call to Product(...) creates a separate object with its own data. You could create a hundred Product instances. Each one would hold different values. They all came from the same class.

This is the pattern you will use most often. Required parameters make it clear what data each object needs to work properly.

Using default values in __init__

Python’s __init__ supports default parameter values, just like any other function. If a parameter has a default, the caller can skip it.

class ServerConfig:
    def __init__(self, host="localhost", port=8000, debug=False):
        self.host = host
        self.port = port
        self.debug = debug

local_config = ServerConfig()
prod_config = ServerConfig(host="api.company.com", port=443)

local_config uses all the defaults. prod_config overrides host and port but keeps debug as False.

Good defaults reduce boilerplate. If 90% of your objects use the same value for a parameter, making it a default saves the caller from repeating it every time.

Defaults should be chosen carefully. A default that hides an important decision can cause real problems. For example, a database connection string defaulting to a production URL. Defaults work best when they represent the safe, common case.

Common mistakes beginners make with __init__

Most __init__ bugs follow a handful of patterns I see often.

MistakeWhat goes wrongBetter approach
Forgetting self in the method definitionPython raises an argument error because the instance has nowhere to goAlways make self the first parameter
Forgetting self. when assigning attributesThe value stays local to the method and disappears after __init__ finishesUse self.name = name to store it on the object
Returning a value from __init__Python raises TypeErrorOnly do setup; do not return anything
Putting too much logic in __init__Objects become harder to test and debugKeep initialization focused on state setup
Using mutable default argumentsInstances can accidentally share state because the default list or dict is created onceUse None and create the mutable object inside the method

The mutable default argument issue deserves a closer look. It catches experienced developers too. Here is the safe pattern:

class ShoppingCart:
    def __init__(self, items=None):
        self.items = items if items is not None else []

If you wrote def __init__(self, items=[]) instead, every ShoppingCart that relies on the default would share the same list object. Adding an item to one cart would add it to all of them.

My rule is simple: __init__ should set up the object, not run half the application.

Best practices for writing a good __init__

Good __init__ methods share a few qualities. They are focused. They are readable. They are predictable.

  • Keep __init__ focused on object setup. Assign attributes. Set initial state. That is enough.
  • Use clear parameter names. Someone reading the class for the first time should understand what each parameter means without jumping to documentation.
  • Validate inputs when the validation belongs to the object. If an Employee should never have a negative salary, it is reasonable to check that here.
class Employee:
    def __init__(self, name, salary):
        if salary < 0:
            raise ValueError("salary must be non-negative")
        self.name = name
        self.salary = salary

  • Prefer sensible defaults over long argument lists. If a class requires eight parameters to instantiate, consider whether some of them have safe defaults.
  • Avoid side effects. API calls do not belong in __init__. Neither do file writes, database queries, or heavy computation. The caller should be able to create an object without triggering external actions.
  • Use a classmethod or factory function for complex creation. If building the object requires multiple steps, move that logic out of __init__. Put it into a dedicated method.
  • Document non-obvious parameters. A short docstring saves future readers real time.

__init__ vs __new__: what matters for most Python learners

You may have seen __new__ mentioned alongside __init__.

MethodMain jobCalled whenCommon useWhat most learners should do
__new__Create a new instanceBefore __init__Advanced cases like immutable types or metaprogrammingKnow it exists, rarely override it
__init__Initialize instance attributesAfter the instance is createdNormal class setupLearn this first and use it often

When you call a class, Python first calls __new__ to create the raw instance. Then it calls __init__ to initialize it. For the vast majority of Python code, you never need to touch __new__. Python’s default behavior handles it.

Custom __new__ methods show up in patterns like singletons and custom metaclasses. They also appear in subclasses of immutable types like str or tuple. These are real use cases. They are not beginner territory. If you want to explore these patterns, advanced Python techniques cover them in depth.

If you are still getting comfortable with Python classes, focus on __init__ first. You can revisit __new__ later if a specific problem requires it.

Inheritance and super().__init__()

When one class inherits from another, the child class often needs to reuse the parent’s initialization logic. That is what super().__init__() does.

class User:
    def __init__(self, username):
        self.username = username

class AdminUser(User):
    def __init__(self, username, permissions):
        super().__init__(username)
        self.permissions = permissions

AdminUser calls super().__init__(username) to let the User class handle storing the username attribute. Then it adds its own permissions attribute on top.

This prevents repeated setup logic. If User.__init__ later adds validation or new attributes, AdminUser picks up those changes automatically without any modification.

You will see super().__init__() frequently in frameworks like Django, Flask, and PyTorch. Any time you subclass a framework-provided class, you almost always call super().__init__(). This lets the parent class set up its internal state before you add yours.

When not to put logic in __init__

Some logic does not belong in __init__. It might seem convenient to put it there, but resist the urge.

Avoid doing any of these inside __init__:

  • Running database queries
  • Making API calls
  • Writing to files
  • Performing slow computations
  • Processing large datasets

When __init__ triggers external actions, object creation becomes unpredictable. Failures happen at construction time. Tests become harder to write. Creating a simple test object suddenly requires a live database or network connection. Setup logic and business logic get tangled together.

Better alternatives exist. Use a classmethod or factory function to handle complex creation. Add a separate method like .load(), .connect(), or .fetch(). The caller invokes it explicitly after the object exists.

__init__ should set up the object. Let the caller decide when to kick off the expensive work.

Real-world uses of __init__

In real projects, __init__ often holds configuration and object state. It does not hold business outcomes.

  • API clients store a base URL, authentication token, and session settings in __init__. Every subsequent request then uses the same configuration.
  • ML experiment classes store model hyperparameters, dataset paths, and training settings. The configuration then travels with the experiment object.
  • Data pipeline configs store environment-specific values. These include file paths, database connection strings, and batch sizes.
  • Web application models store object state, like a user’s profile data or an order’s line items.
  • Service classes store a logger, environment flag, or shared client. Every method on the object can then access them.

The pattern is consistent. __init__ receives configuration and stores it as instance attributes. The object’s methods can then use it later. The actual work, sending requests, training models, processing records, happens in other methods.

That gap between setup and execution is exactly what makes classes useful. Once you see __init__ as the place where an object learns what it needs to know, the rest of object-oriented programming in Python becomes easier to understand.

Quick recap

  • __init__ runs automatically when an object is instantiated.
  • It initializes instance attributes. It does not create the object itself.
  • self refers to the specific object being set up.
  • Default values simplify object creation when sensible defaults exist.
  • __new__ creates the instance; __init__ initializes it afterward.
  • Keep __init__ focused on state setup. Avoid side effects and heavy logic.
  • Use super().__init__() to reuse parent class initialization in child classes.
  • Watch for mutable default arguments. Use None and create the mutable object inside the method.

Build practical Python skills

If you are learning Python for real project work, the next step is using classes, functions, and programs to solve real problems. Reading syntax examples is not enough. Understanding __init__ is foundational. The skill becomes durable when you apply it inside larger projects.

Explore Udacity’s Introduction to Programming Nanodegree and other programming courses to build practical Python skills. Apply them in software, data, and AI workflows.

FAQ

Is __init__ a constructor in Python?

It is commonly called a constructor. Technically, it is an initializer. Python creates the object first using __new__. Then it calls __init__ to set up its attributes. For most practical purposes, you can think of it as the place where object setup happens.

Do I need __init__ in every Python class?

No. If a class does not need instance-specific setup, you can omit __init__ entirely. Python provides a default that does nothing. Classes that only contain class-level attributes or static methods may not need one at all.

Why do I need self in __init__?

self refers to the current instance of the class. Without it, you have no way to store data on the object. When you write self.name = name, you are attaching that value to the specific object being created.

Can __init__ return a value?

No. __init__ must return None. If you try to return anything else, Python raises a TypeError. Its purpose is to initialize the object. It does not produce a result.