OOP in Python, part 3: The __init__() method

MP 39: Another bit of OOP magic.

Note: This is the third post in a series about OOP in Python. The previous post discussed the role of self, and why it’s important. The next post discusses static methods.


After self, one of the most frequent topics people ask about regarding OOP is the __init__() method. What’s special about it? How does it work? And why is it so important to spell __init__ right?

In this post, we’ll take a closer look at __init__(), and answer these kinds of questions.

A class without __init__()

To start, let’s consider whether it’s possible to have a functioning class without an __init__() method.

It’s certainly possible. Here’s a simple class without one:

class Greeter:
    """Greet people in a variety of ways."""

    def say_hello(self):
        print("Hi.")

greeter = Greeter()
greeter.say_hello()

Currently, this class offers one way to greet people, by saying “Hi”:

Hi.

A class doesn’t need an __init__() method. This is a common source of confusion, which we’ll see a little later when we look at what happens when __init__() is accidentally spelled wrong.

A simple __init__() method

Let’s look at a simple __init__() method. We’ll define one attribute for the Greeter class, a tone for the greeting:

class Greeter:
    """Greet people in a variety of ways."""

    def __init__(self, tone="casual"):
        self.tone = tone

    def say_hello(self):
        if self.tone == "casual":
            print("Hi.")
        elif self.tone == "formal":
            print("Hello.")

greeter = Greeter()
greeter.say_hello()

greeter.tone = "formal"
greeter.say_hello()

The __init__() method accepts an optional tone argument, which is set to "casual" by default. If the tone is casual the greeting is “Hi”, and if it’s formal the greeting is “Hello”:

Hi.
Hello.

This works because whenever you make an instance from a class, Python looks to see if the class has a method named __init__(). If it does, Python runs that method automatically.

To make this really clear, consider just these two lines:

greeter = Greeter()
greeter.say_hello()

It looks like only one method, say_hello(), is being called. But when the first line here is executed, Python finds our __init__() method and runs it. Most importantly, the value of self.tone is being set before say_hello() can ever be called.

More than a spelling mistake

One of the most common confusions around the __init__() method comes from simple spelling mistakes. Let’s see what happens if we leave off one of the underscores in the name __init__:

    def _init__(self, tone="casual"):
        self.tone = tone

Here I’ve left off the first underscore in the name __init__(). When the program runs with this error, we get a traceback:

Traceback (most recent call last):
  ...
  File "greeter_misspelled.py", line 8, in say_hello
    if self.tone == "casual":
       ^^^^^^^^^
AttributeError: 'Greeter' object has no attribute 'tone'

The traceback is complaining that self.tone hasn’t been defined. When people see this kind of error they tend to look at their code, see that self.tone is very clearly being defined in what they think is an __init__() method, and end up confused about how to fix the error.

The advice people receive when they share their code is to “fix the spelling of __init__().” Or, when they step away and then look back at their own code they think “Oh, I just made a spelling mistake.” Yes, that’s true, but it often points to a lack of clarity about how the __init__() method is called. It’s not just called; it’s discovered by Python based on its name, and then called whenever a new instance is created.

A more thorough line of thinking that should occur when this error appears would be:

  • The value of self.tone is not being set.
  • It’s set in my __init__() method; my __init__() method must not be executing.
  • Why wouldn’t my __init__() method not be executing?

The only real reason __init__() wouldn’t run is because of a spelling mistake. In many years of helping people learn Python, I’ve seen just about every possible misspelling of __init__(): _init__, __init_, __int__, init, int, _init_, and more. It’s a really easy name to misspell, and I’m pretty sure everybody makes this typo at some point.

If the __init__() method were required, you’d get a clearer error message when you spelled it wrong. The message would read something like: “No __init__() method found.” But it’s not required, so Python can’t really tell that you intended to include it but accidentally misspelled the name of the method.1

You can call it something else (sort of)

It’s helpful to see that you could call __init__() something else. The only caveat is that you’d have to call that method yourself.

Here’s Greeter with an initialize_object() method instead of __init__():

class Greeter:
    """Greet people in a variety of ways."""

    def initialize_object(self, tone="casual"):
        self.tone = tone

    def say_hello(self):
        if self.tone == "casual":
            print("Hi.")
        elif self.tone == "formal":
            print("Hello.")

greeter = Greeter()
greeter.initialize_object()
greeter.say_hello()

This code works exactly as the earlier version did. The only difference is that we’re using a name other than __init__(), so we have to call that method explicitly before calling say_hello(). If you leave out the call to initialize_object(), you’ll get the same traceback we saw earlier.

There’s nothing special about __init__(), other than the fact that Python calls it automatically whenever we make a new instance from a class. Most nontrivial classes require some initialization work, and it’s just plain convenient and easier to have this step done automatically. Most languages that implement OOP have this kind of behavior. Some languages look for a method with the same name as the class, some look for constructor(), and others look for initialize().2

Conclusions

It’s easy to say that the functionality of the __init__() method is pretty simple. It’s simple if you understand what Python is doing on your behalf. If you don’t understand this behind-the-scenes behavior, it can be hard to diagnose issues when things don’t work.

All you really need to remember is that whenever you create a new instance from a class, Python looks for a method called __init__(), and runs that method if it finds one.

Resources

You can find the code files from this post in the mostly_python GitHub repository.


  1. This does make me wonder if the new friendlier error messages could include a check for this. Basically, if running a method generates an AttributeError, and there’s a method included with one of the names (_init__, __init_, __int__, init, int, _init_,...), display the AttributeError along with a message asking if the user meant to include an __init__() method.

  2. Python has one more method that it calls automatically, that you might want to be aware of. When you create a new instance from a class, Python calls its own __new__() method to create the instance. It then calls the __init__() method, if one is present.