Skip to content

Constructors: Initializing Object State with the __init__ Method in Python

Updated: at 03:23 AM

Constructors are special methods in Python classes that are automatically called when an object is instantiated to initialize the object’s state. The most common constructor method is the __init__ method. Defining a __init__ method allows you to set initial values for attributes, run validation, instantiate private attributes, or perform any other one-time configuration required for the object to be properly set up.

Understanding how to properly implement constructors in Python is an essential skill for any Python developer. Constructors lay the foundation for how objects will function in your code by controlling the process of instantiating new class instances. In this comprehensive guide, we will cover the following topics related to constructors and the __init__ method in Python:

Table of Contents

Open Table of Contents

What are Constructors and the __init__ Method?

In Python, a constructor is a special method that is called automatically when an object is instantiated in order to construct or initialize it. The main constructor method is __init__, spelled with two leading and trailing underscores.

The __init__ method enables you to set initial values for attributes, run validation, initialize private attributes, open database connections, set up communication channels or any other required setup when the object is being created.

For example:

class Person:

  def __init__(self, name, age):
    self.name = name
    self.age = age

p1 = Person("John", 36)
print(p1.name)
# Prints "John"

Here, the __init__ method takes name and age parameters and creates name and age attributes on the Person instance p1, initializing it with the given values.

Unlike other languages, Python does not have standalone constructors. The __init__ method is the constructor for Python classes.

When is the __init__ Method Called?

The __init__ method is called every time an instance of a class is created. This happens when you call the class to instantiate it:

my_object = MyClass(args)

or with a literal instantiation:

my_object = MyClass(args)

Behind the scenes, Python will first call __init__ on the newly created my_object, passing in any arguments given during instantiation.

The __init__ call initializes the object’s state before it is used. No other methods can be called on my_object until after __init__ returns.

This behavior allows the class to ensure the new object is properly configured before being used. Custom initialization code goes in __init__.

Using __init__ to Initialize Attributes

The main purpose of __init__ is to initialize any attributes the class needs to function properly.

For example:

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

acct = BankAccount("1234", 500)

Here __init__ takes account_number and an optional balance parameter and sets them as attributes on the account instance.

This is the primary way the method initializes the necessary state. Any attributes that the object needs to function should be initialized in __init__.

You can access these attributes on instantiated objects:

print(acct.account_number) # "1234"
print(acct.balance) # 500

Initializing attributes in __init__ ensures they will be defined whenever a new instance is created.

Setting Default Values for Attributes

You can set default values for any attributes in __init__ by giving them a default parameter value:

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

Now any accounts created without specifying a balance will default to 0:

acct = BankAccount("1234")
print(acct.balance) # 0

Setting reasonable defaults reduces duplicative code when instantiating classes.

You can also set default values using standard conditional logic:

class BankAccount:
  def __init__(self, account_number, balance=None):
    self.account_number = account_number
    if balance is None:
      self.balance = 0
    else:
      self.balance = balance

This allows flexible default values depending on the logic needed.

Validating Data in __init__

The __init__ method is useful for validating data when an object is first created.

For example, you can check that a birthday is a valid date:

from datetime import date

class Person:
  def __init__(self, name, birthday):
    self.name = name
    try:
      birthday = date.fromisoformat(birthday)
    except ValueError:
      raise ValueError("Birthday must be a valid date")
    self.birthday = birthday

p = Person("John", "2018-04-16") # No error
p = Person("John", "abcd") # Raises ValueError

This ensures invalid birthdays raise an exception on initialization.

Other examples of validation include:

Doing validation in __init__ guarantees objects meet requirements at creation.

The self Parameter

The __init__ method always has self as its first parameter.

self refers to the instance being initialized. Inside __init__, you can access attributes and methods on the current object instance through self.

For example:

class Person:
  def __init__(self, name):
    self.name = name

  def introduce(self):
    print(f"Hi, I'm {self.name}")

p = Person("John")
p.introduce() # Hi, I'm John

Here self allowed the introduce() method to access the name attribute set in __init__.

self does not need to be explicitly passed when calling __init__. Python automatically adds the instance being initialized as the first argument.

Overriding the Default Constructor

You can override Python’s default constructor behavior to customize object creation.

For example, you can log instances as they are created:

class LoggedConstructor:
  num_instances = 0

  def __init__(self):
    LoggedConstructor.num_instances += 1
    print(f"Initialized instance {LoggedConstructor.num_instances}")

x = LoggedConstructor() # Initialized instance 1
y = LoggedConstructor() # Initialized instance 2

Or you can limit the number of instances:

class LimitedInstances:
  num_instances = 0
  max_instances = 5

  def __init__(self):
    if LimitedInstances.num_instances >= LimitedInstances.max_instances:
      raise RuntimeError("Exceeded maximum instances")
    LimitedInstances.num_instances += 1

# This works
x = LimitedInstances()

# But additional instances will fail
x = LimitedInstances() # Raises RuntimeError

You have full control over what happens when __init__ is called during instantiation.

Calling Parent Class Constructors with super()

When overriding __init__ in a subclass, remember to call the parent class’s version using super():

class Employee:
  def __init__(self, name, id):
    self.name = name
    self.id = id

class Manager(Employee):
  def __init__(self, name, id, team_size):
    super().__init__(name, id)
    self.team_size = team_size

This ensures the parent Employee class’s __init__ method sets up its required attributes.

Forgetting to call super() will prevent initialization of the parent portion of the subclass.

Constructor Overloading in Python

Unlike some languages, Python does not support constructor overloading.

Constructor overloading is having multiple constructors with the same name but different parameters. Python will not allow you to define __init__ more than once in a class.

Instead, you can simulate overloading by defining a single __init__ that accepts all possible parameters. For example:

class Rectangle:
  def __init__(self, width=0, height=0):
    self.width = width
    self.height = height

# Usage:
rect1 = Rectangle() # 0x0 rectangle
rect2 = Rectangle(5, 3) # 5x3 rectangle

This allows flexible construction without true method overloading.

Common Python Constructor Patterns

There are some common and useful patterns to be aware of when implementing __init__ constructors in Python:

Following these patterns will lead to properly initialized and safely constructed objects in Python.

Conclusion

The __init__ constructor method is called automatically in Python each time an instance of a class is instantiated. It is used to initialize any attributes, validate parameters, set defaults, establish connections, and perform any other setup required for the proper functioning of the object.

Key takeaways include:

Properly initializing object state with constructors is crucial to building robust and usable classes in Python. Following Python’s constructor best practices will improve your code’s structure, reliability, and maintainability.