Skip to content

Python __init__ Method and self

Updated 7 min read Aimerscrew

In the previous lesson, you created objects and then set their attributes one line at a time. That gets tiring fast, and it's easy to forget one. The Python __init__ method solves this: it runs automatically whenever a new object is created and sets up all of its data in one go. In this lesson, you'll learn how __init__ works, what self really is, and how to use default values.

🌍 Real-life example

When you open a bank account, the bank doesn't hand you an empty folder and hope you fill it in later. The moment the account is created, it's set up with your name, an account number and a starting balance. Signing up for a streaming service works the same way: your username and email are recorded right away, and your plan starts as "Free" unless you choose otherwise. __init__ is that setup step for Python objects.

What is the __init__ method in Python?

__init__ (pronounced "dunder init", short for "double underscore init") is a special method that Python calls automatically right after a new object is created. Its job is to initialize the object, which means giving it its starting attributes. Many programmers call it the constructor.

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

account = BankAccount("Maya", 100)

print(account.owner)
print(account.balance)
Maya
100

You never call __init__ yourself. When you write BankAccount("Maya", 100), Python creates an empty object, passes it to __init__ as self along with "Maya" and 100, and then hands the finished object back to you.

Animation in three steps: BankAccount("Maya", 100) creates an empty object, Python calls __init__ with self as that object, owner as Maya and balance as 100, then the filled object with owner and balance is stored in the variable account
What happens when you call a class: create an empty object, run __init__ on it, return it.

What is self in Python?

self is the object a method is working on. Inside __init__, it's the brand-new object being set up. Inside any other method, it's the object you called the method on.

The line self.owner = owner may look odd at first, but it reads naturally: "store the owner argument as this object's owner attribute". The name on the right is a normal parameter that disappears when __init__ finishes; the attribute on the left stays attached to the object.

Here's proof that self is just the object. These two calls do exactly the same thing:

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

    def deposit(self, amount):
        self.balance = self.balance + amount
        print(self.owner, "now has $", self.balance)

account = BankAccount("Maya", 100)

account.deposit(50)                  # the normal way
BankAccount.deposit(account, 25)     # what Python does behind the scenes
Maya now has $ 150
Maya now has $ 175

account.deposit(50) is a shortcut for BankAccount.deposit(account, 50). Python fills in self for you, which is why you never pass it yourself.

💡 Tip

self isn't a keyword; it's just a naming convention. Python would accept any name for the first parameter, but every Python programmer uses self, and you should too. Code that uses another name is confusing to read.

Using default values in __init__

__init__ is a function, so its parameters can have default values. You can also create attributes that don't come from any argument at all:

class User:
    def __init__(self, username, email, plan="Free"):
        self.username = username
        self.email = email
        self.plan = plan
        self.is_active = True     # every new user starts active

    def summary(self):
        print(self.username, "|", self.email, "|", self.plan, "| active:", self.is_active)

sam = User("sam_k", "sam@example.com")
lea = User("lea99", "lea@example.com", "Premium")

sam.summary()
lea.summary()
sam_k | sam@example.com | Free | active: True
lea99 | lea@example.com | Premium | active: True

Sam didn't choose a plan, so he gets the default "Free". Lea passed "Premium", which replaces the default. Both get is_active = True automatically.

Checking values inside __init__

Because __init__ runs for every new object, it's the perfect place to reject bad data before it causes problems later. You can raise an exception when something is wrong:

class BankAccount:
    def __init__(self, owner, balance=0):
        if balance < 0:
            raise ValueError("Starting balance cannot be negative")
        self.owner = owner
        self.balance = balance

ok = BankAccount("Noah")
print(ok.owner, ok.balance)

bad = BankAccount("Zoe", -20)
Noah 0
ValueError: Starting balance cannot be negative

What happens when you print an object?

If you print one of your objects directly, Python doesn't know what to show, so it prints the class name and a memory address:

account = BankAccount("Maya", 100)
print(account)

You'll see something like this (the hexadecimal number is a memory address and will be different on your computer and every time you run the program):

<__main__.BankAccount object at 0x000001A2B3C4D5E0>

That isn't very helpful. For now, print the attributes you care about, or write a method like summary() above. Later, in Python magic methods, you'll learn to add a __str__ method so print(account) shows a friendly message instead.

Real-world example: a coffee order

Here's a small, complete class that uses everything from this lesson: required arguments, defaults, an attribute calculated in __init__, and a method that uses self.

class CoffeeOrder:
    PRICES = {"small": 3.0, "medium": 3.5, "large": 4.0}

    def __init__(self, customer, size="medium", extra_shot=False):
        self.customer = customer
        self.size = size
        self.extra_shot = extra_shot
        self.price = self.PRICES[size]
        if extra_shot:
            self.price = self.price + 0.75

    def receipt(self):
        print(f"{self.customer}: {self.size} coffee, ${self.price:.2f}")

CoffeeOrder("Ava").receipt()
CoffeeOrder("Liam", "large", extra_shot=True).receipt()
Ava: medium coffee, $3.50
Liam: large coffee, $4.75

PRICES is defined inside the class but outside any method, so all orders share it. That's called a class attribute, and it's the topic of the next lesson.

Common mistakes with __init__ and self

1. Forgetting self as the first parameter

class Player:
    def __init__(name, score):
        name.score = score

p = Player("Kai", 10)
TypeError: Player.__init__() takes 2 positional arguments but 3 were given

Python passes the new object plus your two arguments, which makes three. Always start with self: def __init__(self, name, score):.

2. Forgetting self. when storing an attribute

class Player:
    def __init__(self, name):
        name = name           # creates a local variable, not an attribute

p = Player("Kai")
print(p.name)
AttributeError: 'Player' object has no attribute 'name'

Without self., the value is stored in a local variable that vanishes when __init__ ends. Write self.name = name.

3. Misspelling __init__

class Player:
    def _init_(self, name):   # only one underscore on each side
        self.name = name

p = Player("Kai")
TypeError: Player() takes no arguments

The name must be exactly __init__, with two underscores before and after. A typo means Python never finds your setup method.

4. Leaving out a required argument

class Player:
    def __init__(self, name, score):
        self.name = name
        self.score = score

p = Player("Kai")
TypeError: Player.__init__() missing 1 required positional argument: 'score'

Pass every argument that has no default, or give the parameter a default value like score=0.

5. Returning a value from __init__

__init__ must not return anything. Writing return self at the end causes TypeError: __init__() should return None, not 'Player'. Python already returns the new object for you.

Practice exercise

🏋️ Try it yourself

Create a Flight class whose __init__ takes a number, a destination and seats (default 180). Add a method book(count) that subtracts from seats and prints how many are left. Create flight "BA117" to "New York", then book 2 seats and then 5 seats.

Show solution
class Flight:
    def __init__(self, number, destination, seats=180):
        self.number = number
        self.destination = destination
        self.seats = seats

    def book(self, count):
        self.seats = self.seats - count
        print(self.number, "to", self.destination, "- seats left:", self.seats)

flight = Flight("BA117", "New York")
flight.book(2)
flight.book(5)
BA117 to New York - seats left: 178
BA117 to New York - seats left: 173

What's next?

Every object now gets its own data from __init__. But some data should be shared by all objects of a class. Learn how in Python class vs instance attributes.

Key takeaways

  • __init__ is a special method that runs automatically every time a new object is created.
  • Arguments passed to the class, like BankAccount("Maya", 100), go straight to __init__.
  • self is the object being created or used; self.name = name stores data on that object.
  • obj.method(x) is a shortcut for ClassName.method(obj, x), which is why you never pass self yourself.
  • Parameters of __init__ can have default values, and __init__ can also create attributes that do not come from arguments.
  • __init__ must be spelled with two underscores on each side and must not return a value.

Frequently asked questions

What does __init__ do in Python?

It initializes a new object. Python calls it automatically right after creating the object, and you use it to set the object's starting attributes, usually from the arguments passed to the class.

Is __init__ a constructor in Python?

It is commonly called the constructor. Technically, Python first creates the object with a method called __new__ and then calls __init__ to set it up, but for everyday code you can think of __init__ as the constructor.

Why do we use self in Python?

self gives a method access to the specific object it was called on, so it can read and change that object's attributes. Python passes the object automatically as the first argument.

Is self a keyword in Python?

No. self is only a strong convention for the name of the first parameter of a method. Python would accept another name, but always using self keeps code readable.

Can a class have no __init__ method?

Yes. If you do not write one, Python uses a default that takes no arguments, so you can create the object with ClassName() and add attributes later.

Can __init__ return a value?

No. __init__ must return None. Returning anything else raises a TypeError, because Python itself returns the new object to the caller.