We’ll now look at a special Python function that is part of most classes: the Python constructor. A constructor is a function that is called automatically when an object is created. A constructor can optionally accept arguments as well, just like a regular function.
The default constructor
When creating an object from a class, it looks like we are calling a function:
car = Car()Code language: Python (python)
Well… it doesn’t just look like we are calling a function, we are in fact calling a function! This method, which we did not have to define, is called the constructor. It constructs and initializes the object. Every class has one by default, called __init__, even if we don’t define it ourselves. This has to do with inheritance, which you’ll learn about shortly.
Have you ever used the str() function to convert a number into a string? Or perhaps the int() function to convert a string into a number?
>>> 'a' + str(1)
'a1'
>>> int('2') + 2
4Code language: Python (python)
What you are doing here, is creating new objects of type str and int by calling the constructors of the classes str and int.
Creating your own Python constructor
We can override the __init__ method, to give it extra abilities by accepting arguments. Let’s redefine the Car class using a custom constructor:
class Car:
def __init__(self, started = False, speed = 0):
self.started = started
self.speed = speed
def start(self):
self.started = True
print("Car started, let's ride!")
def increase_speed(self, delta):
if self.started:
self.speed = self.speed + delta
print("Vrooooom!")
else:
print("You need to start the car first")
def stop(self):
self.speed = 0Code language: Python (python)
Our custom Python constructor has named parameters with default values, so we can create instances of the class Car in multiple ways:
>>> c1 = Car()
>>> c2 = Car(True)
>>> c3 = Car(True, 50)
>>> c4 = Car(started=True, speed=40)Code language: Python (python)
You may have noticed a flaw: we can now create a new car that is not started but set its speed anyway. Let’s fix that next.
Validating arguments in the constructor
The constructor is the perfect place to check that an object starts out in a valid state. If the arguments don’t make sense, raise an exception. A ValueError is the usual choice for a value that’s wrong:
class Car:
def __init__(self, started=False, speed=0):
if speed < 0:
raise ValueError("speed can't be negative")
if speed > 0 and not started:
raise ValueError("a car that isn't started can't have a speed")
self.started = started
self.speed = speedCode language: Python (python)
I left out the other methods to keep the example short. Now, creating a car that drives without being started fails right away, with a clear message:
>>> Car(False, 30)
Traceback (most recent call last):
...
ValueError: a car that isn't started can't have a speedCode language: Python (python)
This is better than finding out much later that your program works with a car in an impossible state.
__init__ always returns None
Notice that __init__ has no return statement. It doesn’t need one: Python creates the object, passes it to __init__ as self, and gives you that object when __init__ is done. In fact, __init__ isn’t allowed to return anything other than None:
>>> class Broken:
... def __init__(self):
... return 42
...
>>> Broken()
Traceback (most recent call last):
...
TypeError: __init__() should return None, not 'int'Code language: Python (python)
Calling the parent constructor with super()
When a class inherits from another class and defines its own __init__, the parent’s __init__ no longer runs automatically. You have to call it yourself with super().__init__(), so the parent can set up its part of the object:
class ElectricCar(Car):
def __init__(self, started=False, speed=0, battery_level=100):
super().__init__(started, speed)
self.battery_level = battery_levelCode language: Python (python)
>>> e = ElectricCar(True, 30)
>>> e.started, e.speed, e.battery_level
(True, 30, 100)Code language: Python (python)
If you forget the super().__init__() call, the object won’t have the started and speed attributes, and the validation in Car never runs.
Alternative constructors with @classmethod
A class can only have one __init__. If you want more ways to create an object, for example from a string or a file, add a class method that builds the object for you. By convention, its name starts with from_:
class Car:
def __init__(self, started=False, speed=0):
self.started = started
self.speed = speed
@classmethod
def from_string(cls, text):
started, speed = text.split(",")
return cls(started == "on", int(speed))Code language: Python (python)
>>> car = Car.from_string("on,60")
>>> car.started, car.speed
(True, 60)Code language: Python (python)
A class method gets the class as its first argument, called cls, instead of an instance. Calling cls(...) runs the regular constructor, so the object is still set up by __init__. Python itself uses this pattern too, for example in dict.fromkeys() and datetime.date.fromisoformat().
__new__ vs __init__
Strictly speaking, __init__ is the initializer. The method that creates the object is called __new__. When you call Car(), Python first calls __new__ to create an empty object, and then __init__ to fill it in:
class Point:
def __new__(cls, x, y):
print("__new__ creates the object")
return super().__new__(cls)
def __init__(self, x, y):
print("__init__ initializes it")
self.x = x
self.y = y
p = Point(1, 2)Code language: Python (python)
Output:
__new__ creates the object
__init__ initializes itCode language: Python (python)
You’ll almost never need to write __new__ yourself. It’s for special cases, like subclassing immutable types such as int or str. In everyday Python, people call __init__ the constructor, and so do I in this article.
Skip the boilerplate with dataclasses
Many constructors do nothing more than copy their arguments to attributes, like self.speed = speed. For classes like that, a dataclass writes the __init__ for you, based on type hints:
from dataclasses import dataclass
@dataclass
class Car:
started: bool = False
speed: int = 0Code language: Python (python)
>>> Car(True, 50)
Car(started=True, speed=50)Code language: Python (python)
As a bonus, you get a readable representation and a working == comparison. You can still add your own methods to a dataclass, and put validation in a method called __post_init__, which runs right after the generated __init__.