AlgoMaster Logo

__init__ Method

High Priority20 min readUpdated June 6, 2026
Listen to this chapter
Unlock Audio

A constructor is the method Python runs the instant a new object comes to life. For classes built around real data (a Product with a name and a price, a Customer with an email, an Order with an item list), the constructor is where that data gets attached to the instance so the rest of the program can use it. This lesson covers what __init__ does, how to write one, how to set sensible defaults, how to validate inputs, and the small set of mistakes that bite almost everyone the first few times they write a class.

What __init__ Actually Is

__init__ is a regular method with a special name. Python looks for it on the class whenever you call the class like a function. The call Product("Mouse", 29.99) is doing two things in sequence: it builds a brand-new, empty Product object, and then it hands that object to __init__ so the method can fill it in. By the time the call returns, you have an instance whose attributes are already populated.

Here is the smallest useful example:

Three things are worth pointing out here. First, __init__ is never called as __init__(...) directly; you call the class, and Python calls __init__ for you. Second, the first parameter is always self, which is the new instance Python just created and is now passing in for the method to set up. Third, every self.something = value line attaches an attribute to the instance, so after the constructor finishes, the object carries that data with it for the rest of its life.

Most of the time, that is the whole job: take some arguments, validate them, store them on self. A class without an __init__ is legal Python, but it's rare in practice, because almost every class you write exists to hold some state, and the constructor is the natural place to receive that state.

When __init__ Runs

__init__ runs exactly once per object, immediately after Python creates the empty instance and before the call expression that produced it returns. You don't trigger it; the moment you write ClassName(...), the constructor fires. There is no separate "create" then "initialize" step you have to remember; Python wires the two together.

A quick way to see this is to print from inside __init__:

The print inside __init__ lands between the two outer prints, which is exactly where the constructor runs. The call expression Product("Wireless Mouse", 29.99) doesn't return until the constructor finishes setting up the instance.

Two consequences fall out of this. The constructor runs once per instance, not once per class, so creating ten Product objects calls __init__ ten times, each with its own self. And anything that happens during __init__ (printing, opening files, reading config) happens every time you build an instance, which is one reason to keep the constructor focused on assignment rather than expensive setup.

The diagram shows the full path of a Product("Mouse", 29.99) call. The two methods Python uses behind the scenes are __new__ (which builds the empty object) and __init__ (which fills it in). For almost every class you write, you only need to define __init__ and let Python's default __new__ handle the creation step.

Defining __init__ With Parameters

The parameter list of __init__ is the shape of the constructor call. Whatever you list there, after self, is what callers have to provide when they build an instance. Add a parameter and the call gains an argument; remove one and it loses one.

Three parameters in the constructor, three arguments at the call site, three attributes on the instance. The names don't have to match (you could write self.name = name_arg if the parameter were called name_arg), but matching them is the common convention and reads cleanly.

Constructors accept positional and keyword arguments just like any other function:

Keyword arguments are useful once a constructor takes more than two or three parameters, because the call site stops looking like a row of mystery values. If you read Order(102, "Bob", "shipped") cold, you have to remember the parameter order. Order(order_id=102, customer_name="Bob", status="shipped") tells you what each value is on sight.

You can also accept *args and **kwargs in __init__ if you genuinely need a variable number of arguments, but this is rare for everyday classes and usually a sign that the design wants to be reconsidered. Most constructors take a small, fixed set of named parameters.

Setting Instance Attributes Inside __init__

The body of __init__ is the natural place to attach data to the new instance. Every assignment to self.something inside the constructor creates an attribute on that specific instance. The same attribute name on two different objects refers to two different values, because each call to __init__ has its own self.

Each Product carries its own name, price, and stock. Changing one instance's stock does not touch the other, because self in each call pointed at a different object.

You can also compute attributes inside __init__, not just store the parameters as-is. A common pattern is to derive one attribute from the others:

subtotal and total are not constructor parameters; they're computed from items and tax_rate and stored alongside them. The caller doesn't have to compute them; the constructor does it once and the values are then available as attributes for the life of the instance.

One catch worth knowing now: these computed attributes are a snapshot taken at construction time. If the caller later mutates cart.items (appends to it, for example), cart.subtotal does not update on its own; it's just a number that was assigned during __init__. Keeping derived values in sync with their source is its own design question, and the @property decorator is one common answer. For this lesson, the takeaway is just that __init__ sets attributes once, not continuously.

Default Parameter Values

If a constructor parameter has a sensible default, you can give it one in the parameter list, and callers who don't care about that parameter can leave it out:

The mouse call only provides the two required parameters, so stock defaults to 0 and category to "general". The keyboard call overrides both defaults. This is the same default-argument behavior any Python function has; __init__ is not special in that regard.

Defaults make a class friendlier to call when only a few attributes vary from one instance to the next. They also document the "normal" or "expected" value of an attribute at a glance: reading stock=0 in the signature tells you that a freshly created product starts with no stock.

The Mutable Default Argument Trap

There is one trap with default arguments that hits people hard the first time, and it bites just as much inside __init__ as in any other function. Don't use a mutable object (a list, dict, or set) as a default value.

What's wrong with this code?

Both carts share the same list. The default value [] is evaluated exactly once, when the def statement runs (which is when the class is being defined), and that single list object becomes the default for every future call that doesn't pass items. So cart_a.items and cart_b.items are the same list. Mutating it through one cart shows up through the other.

The fix is to use None as the sentinel default and build a fresh container inside the body:

Now every caller that omits items gets a brand-new empty list, because the [] runs each time the body executes. None is fine as a default because None is immutable; the trap only applies to mutable defaults like lists, dicts, and sets.

Validating Input Inside __init__

The constructor is a good place to reject obviously bad data. If a Product price can't be negative and a Review rating must be between 1 and 5, the constructor can check those rules and raise an exception when they're violated. That stops broken objects from coming into existence in the first place, which is much easier to reason about than catching the error later when some downstream code trips over it.

The first construction succeeds because all three values pass the checks. The second call fails the price < 0 check, and the constructor raises ValueError before any attributes are set. Callers can then catch that error and handle it (log it, return a friendly message, retry with corrected input).

A few small rules for validation inside __init__:

  • Validate before assigning. Check the values first, then assign to self. If you assign and then raise, you leave the half-built object in an unclear state, even though Python is about to discard the reference; the order also makes the code easier to read.
  • Raise the right exception type. ValueError for bad values, TypeError for wrong types, KeyError for missing keys. Don't use bare Exception.
  • Include the bad value in the message. "price must be non-negative, got -5.0" is much more debuggable than "invalid price".

Another example with rating bounds:

Two different mistakes, two different exception types. A caller can tell them apart in a try/except and react accordingly. The constructor draws the line: by the time an instance exists, you know its rating is an integer in the valid range.

__new__ vs __init__ (Brief)

Python actually uses two methods to build an object, not one. __new__ creates the empty instance, and __init__ fills it in. The class call (Product(...)) calls __new__ first, which returns a new (typically empty) object of the class, and then Python passes that object to __init__ as self.

For almost every class you write, you don't define __new__. Python's default version, inherited from object, just allocates a new instance and hands it back, which is the right behavior 99% of the time. You write __init__ and ignore __new__ entirely.

A minimal illustration:

The order makes the split visible: __new__ returns the empty object, then __init__ is handed that object as self and fills in the attributes. The two methods together are what the single call Product("Wireless Mouse", 29.99) actually triggers.

__new__ becomes interesting in a narrow set of cases: subclassing immutable types like int or tuple (where the value has to be set during creation, not after), implementing the singleton pattern, or building objects through a factory that returns instances of a different class. None of those are everyday work, and the moment you reach for __new__, you've left beginner Python.

For this section, here is the one-line summary to carry forward: __init__ is the constructor you write, __new__ is the creator Python runs first, and you can safely ignore __new__ until you have a concrete reason to override it.

Common Mistakes

A handful of mistakes show up over and over in newly written __init__ methods. Recognizing them early saves a lot of time.

Forgetting self

The first parameter of every method, including __init__, has to be self. Drop it, and Python's error message is not the one most people expect:

The error is confusing because the call passes two arguments, but Python is counting three: the implicit instance Python created plus the two values from the call. The fix is to put self back as the first parameter:

self is not a keyword; it's just a parameter name, and you could technically call it this or instance or any other valid identifier. Don't. Every Python codebase calls it self, and breaking that convention only makes your code harder to read.

Assigning to a Local Instead of self

Inside the constructor body, an assignment without self. creates a local variable that vanishes when the method returns. It does not attach anything to the instance.

What's wrong with this code?

name = name just reassigns the local parameter to itself. The instance never gets a name attribute, so reading mouse.name later fails. The fix is to prefix the left-hand side with self.:

The right-hand side is the parameter; the left-hand side, with self., is the attribute Python creates on the new instance. The two pieces have the same word in them, but they're different things.

Mutable Default Arguments

We already covered this one in the defaults section, but it deserves a second mention here because it is the single most common bug in newly written constructors. If a parameter's default is a list, dict, or set, every instance that uses the default shares the same object. Always use None as the sentinel and build the container inside the body.

The same trap applies to {} (use None and assign {} inside) and to set() (use None and assign set() inside).

Doing Too Much in __init__

The constructor's job is to put the instance into a usable state, not to run the program. Heavy operations like reading large files, making network calls, or building expensive data structures should not happen in __init__. They make every object slow to create, hard to test (you can't construct one without the slow operation also running), and surprising to use.

A cleaner separation is to keep __init__ focused on assigning attributes from its arguments and to put the expensive work behind a method or a class method the caller invokes when needed:

The point is just that __init__ is best kept small and focused. Save the heavy lifting for explicit methods or factories.

A reasonable rule of thumb: if __init__ has more than a dozen lines and isn't mostly assignments to self, it is probably doing too much.

Quiz

__init__ Method Quiz

10 quizzes