Today, we’re talking about constructors! Specifically, we’ll look at the __init__()
special method in Python, which functions as a constructor. Let’s get into it.
Table of Contents
- Concept Overview
- How Do I Use a Constructor?
- How Do I Set Default Arguments in a Constructor?
- How Do I Overload the Constructor?
- Constructing Your Future
Concept Overview
In the world of object-oriented programming languages, we come across this concept known as a constructor. Put as simply as I can, a constructor creates an object.
One way I like to think about this is in terms of construction sites, like building a house. In this analogy, the house is built from a blueprint (i.e., the instructions for how to build a house), and a blueprint is like a class. Therefore, the construction worker is the one who actually builds the house—like how a constructor builds an object.
Extending the analogy, a construction worker can use the same blueprint to make many different houses. Much like how a constructor can use the same class to make many different objects.
With all that said, constructors only make sense when a class is involved. As a result, to write a constructor in Python, we need a class. For today, we’ll keep the house theme and make a basic House class:
class House: pass
Right now, this class doesn’t do anything because it has no fields or methods (beyond the default fields and methods). We can give it some more interesting behavior by creating the constructor:
class House: def __init__(self): pass
Right off the bat, you’ll probably recall that we’re looking at a special method. Again, it doesn’t really do anything. To give it more interesting behavior, we have to add parameters to the method. To do that, we’ll pick a couple high-level attributes of housing, like the number of bedrooms and baths:
class House: def __init__(self, num_beds, num_baths): pass
Of course, the constructor isn’t quite finished. We need to make use of these parameters, so let’s make some instance variables:
class House: def __init__(self, num_beds, num_baths): self.num_beds = num_beds self.num_baths = num_baths
And, that’s it! We have a House which we can construct with different numbers of beds and baths.
Naturally, that answers the question of what a constructor is, but I’m certain you have more questions now. Let’s take a look at a few.
How Do I Use a Constructor?
Once we have a class with a constructor, it’s only useful if we can turn it into an object. To do that, we just need to use the class name directly:
my_house = House(3, 1.5) your_house = House(5, 2)
Again, that’s it! We now have two different house objects with different numbers of bedrooms and baths.
How Do I Set Default Arguments in a Constructor?
Because the constructor is just a special method, it allows for all the same features of other methods. Therefore, if you want some sort of default behavior for your constructor, you can set defaults to the constructor parameters:
class House: def __init__(self, num_beds=3, num_baths=2): self.num_beds = num_beds self.num_baths = num_baths
With the defaults setup, a user can use your constructor in a variety of new ways:
my_house = House(3, 1.5) # provide all arguments in order your_house = House(5) # provide only the first argument their_house = House(num_baths=1) # provide keyword arguments default_house = House() # provide no arguments at all
How Do I Overload the Constructor?
For better or worse, Python doesn’t really have support for method overloading. If you’re not familiar with this concept, it’s basically the idea that we can have multiple methods with the same name but different parameters.
A common use case for overloading might be to have an empty constructor that initializes the fields to some defaults, a basic constructor with whatever you want the user to provide, and a copy constructor which takes in an object of the same type and copies it.
If you want to replicate this behavior, you’re somewhat stuck doing it through the constructor parameters directly. For example, you might expand the constructor to include the option for a house. If that parameter exists, you treat the constructor like a copy constructor. Otherwise, you prioritize the other parameters:
class House: def __init__(self, num_beds=3, num_baths=2, house_copy=None): self.num_beds = num_beds self.num_baths = num_baths if house_copy: self.num_beds = house_copy.num_beds self.num_baths = house_copy.num_baths
As you can imagine, this leads to a lot of weirdness. For instance, what if the user provides all three arguments? Maybe you throw an error, or maybe you silently let the house overwrite the values.
Other ways I’ve seen this done is through a process of manual type checking, but that seems like a bit of a nightmare, especially if you’re looking to accept more than one parameter.
After some searching around, it looks like the Python developers discussed adding some features to allow for method overloading, but I don’t believe they’ve ever been added. Though, it looks like there’s an @overload
decorator in the typing module that may provide this feature, but I haven’t toyed with it. It also looks like there is some single dispatch feature released in the Python 3.4. Ultimately, I’m not sure if any of these features apply to special methods like the constructor. Feel free to explore and let me know!
Alternatively, you can make use of the @classmethod
decorator to provide (in Java terms) static methods that produce objects in certain initial states. Of course, rather than calling the House()
constructor, you would make a static method call like House.new_house()
:
class House: def __init__(self, num_beds=3, num_baths=2): self.num_beds = num_beds self.num_baths = num_baths @classmethod def from_house(cls, house_copy): return cls(house_copy.num_beds, house_copy.num_baths)
Ultimately, it’s up to you to figure out what makes the most sense for your work.
Constructing Your Future
As expected, introducing the concept of constructors opens up a massive can of worms. In the future, I’ll need to cover concepts like objects, methods, and parameters. I’m already enjoying watching the concept map grow!
While you’re here, I’d love it if you read some of these related articles:
- What Are Special Methods in Python?
- The Difference Between str() and repr() in Python: A Design by Contract Perspective
- How to Convert an Integer to a String in Python: Type Casting and f-Strings
In addition, you can build up your Python knowledge by getting one of these (#ad):
- Effective Python: 90 Specific Ways to Write Better Python
- Python Tricks: A Buffet of Awesome Python Features
- Python Programming: An Introduction to Computer Science
If you really enjoy the work, I’d recommend checking out my list of ways to grow the site. Come be a part of the community by joining our Discord or hopping on the Patreon. Otherwise, take care! I’ll see you soon.
Recent Code Posts
Unpacking the Jargon Around Compilers, Interpreters, and More
Today, we're going to get a little pedantic and try to define concepts like compiler and interpreter. Of course, I ultimately don't think it matters what the exact definitions are for practical...
Generally, people think of Python as a messy language because it lacks explicit typing and static type checking. But, that's not quite true in modern times. Surely, we can take advantage of type...