How to create a constructor in Python
Learn how to create constructors in Python. Explore different methods, get practical tips, see real-world examples, and debug common errors.

In Python, constructors initialize object states. The special __init__ method automatically runs when you create a new class instance, which sets up its initial attributes and properties.
In this article, you'll learn techniques to define constructors effectively. You will also find practical tips, see real-world applications, and get advice to debug common issues with your constructors.
Basic __init__ constructor
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person = Person("Alice", 30)
print(f"Name: {person.name}, Age: {person.age}")--OUTPUT--Name: Alice, Age: 30
The __init__ method serves as the constructor for the Person class. Its primary role is to bind data to the specific instance being created. The self parameter is crucial—it’s a reference to this new object, which lets you assign the passed name and age as instance attributes.
This design ensures each Person object starts with its own distinct state. When you call Person("Alice", 30), Python automatically runs __init__, passing the new instance as the self argument along with "Alice" and 30.
Basic constructor techniques
Building on the basic __init__ method, you can enhance your constructors with default parameters, type hints, and validation to make them more robust and flexible.
Using constructors with default parameters
class Product:
def __init__(self, name, price=0.0, stock=0):
self.name = name
self.price = price
self.stock = stock
apple = Product("Apple", 1.25, 100)
default_product = Product("Unknown")
print(f"{apple.name}: ${apple.price}, Stock: {apple.stock}")
print(f"{default_product.name}: ${default_product.price}, Stock: {default_product.stock}")--OUTPUT--Apple: $1.25, Stock: 100
Unknown: $0.0, Stock: 0
Setting default values for parameters like price=0.0 and stock=0 in your __init__ method adds flexibility. It means you don't have to supply every argument when creating an object. While name is required, other attributes can be optional.
- When you create an instance like
Product("Apple", 1.25, 100), you're providing specific values that override the defaults. - If you only provide the required argument, as in
Product("Unknown"), the constructor automatically assigns the default values topriceandstock.
Constructors with type hints
from typing import Optional, List
class Student:
def __init__(self, name: str, age: int, grades: Optional[List[float]] = None):
self.name = name
self.age = age
self.grades = grades if grades else []
student = Student("Bob", 21, [90.5, 85.0, 92.3])
print(f"Student: {student.name}, Age: {student.age}, Grades: {student.grades}")--OUTPUT--Student: Bob, Age: 21, Grades: [90.5, 85.0, 92.3]
Type hints add a layer of clarity to your constructors. They're like labels that declare the expected data type for each parameter, such as name: str. While Python doesn't enforce these types at runtime, they make your code self-documenting and easier for tools to analyze.
- The
gradesparameter is typed asOptional[List[float]], which signals that it can be either a list of floats orNone. - The expression
self.grades = grades if grades else []safely handles the default value. It creates a new empty list for each student who doesn't have grades, preventing different objects from sharing the same list by mistake.
Constructors with validation
class BankAccount:
def __init__(self, account_number, balance):
if not isinstance(account_number, str) or len(account_number) != 10:
raise ValueError("Account number must be a 10-character string")
if balance < 0:
raise ValueError("Balance cannot be negative")
self.account_number = account_number
self.balance = balance
account = BankAccount("1234567890", 1000)
print(f"Account: {account.account_number}, Balance: ${account.balance}")--OUTPUT--Account: 1234567890, Balance: $1000
Validation within the __init__ method ensures your objects are created with valid data from the start. It’s a gatekeeper, checking inputs before they're assigned to instance attributes.
- The constructor for
BankAccountverifies that theaccount_numberis a 10-character string and thebalanceisn't negative. - If the data fails these checks, the constructor raises a
ValueError, which stops the object from being created with incorrect information. This practice helps maintain data integrity throughout your application.
Advanced constructor patterns
With a solid foundation in basic constructors, you can now explore advanced patterns like alternative constructors, flexible arguments with *args and **kwargs, and automatic generation.
Alternative constructors with class methods
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
@classmethod
def from_string(cls, date_string):
year, month, day = map(int, date_string.split('-'))
return cls(year, month, day)
date1 = Date(2023, 10, 15)
date2 = Date.from_string("2023-10-15")
print(f"Date 1: {date1.year}-{date1.month}-{date1.day}")
print(f"Date 2: {date2.year}-{date2.month}-{date2.day}")--OUTPUT--Date 1: 2023-10-15
Date 2: 2023-10-15
Alternative constructors provide flexible ways to create objects beyond the standard __init__. In the Date class, the @classmethod decorator defines from_string as a secondary way to build an instance.
- The main
__init__constructor requires separate integers for the year, month, and day. - The
from_stringmethod accepts a single date string, parses it, and then calls the primary constructor usingcls(year, month, day).
Here, cls is a reference to the Date class itself. This pattern is useful for creating objects from different data sources.
Constructors with *args and **kwargs
class ConfigManager:
def __init__(self, config_file, *args, **kwargs):
self.config_file = config_file
self.args = args
self.kwargs = kwargs
config = ConfigManager("settings.ini", "arg1", "arg2", debug=True, env="production")
print(f"Config file: {config.config_file}")
print(f"Args: {config.args}")
print(f"Kwargs: {config.kwargs}")--OUTPUT--Config file: settings.ini
Args: ('arg1', 'arg2')
Kwargs: {'debug': True, 'env': 'production'}
Using *args and **kwargs in your __init__ method creates a flexible constructor that can handle a variable number of inputs. After accounting for required parameters like config_file, the constructor neatly organizes any remaining arguments.
*argscaptures all extra positional arguments (like"arg1"and"arg2") into a tuple.**kwargscollects all extra keyword arguments (likedebug=True) into a dictionary.
This approach is ideal when you need to pass an open-ended set of configuration options to an object upon its creation.
Using dataclasses for automatic constructors
from dataclasses import dataclass, field
from typing import List
@dataclass
class Employee:
name: str
id: int
department: str
skills: List[str] = field(default_factory=list)
employee = Employee("John Doe", 12345, "Engineering", ["Python", "SQL"])
print(employee)--OUTPUT--Employee(name='John Doe', id=12345, department='Engineering', skills=['Python', 'SQL'])
The @dataclass decorator is a powerful shortcut for creating classes that primarily store data. It automatically generates a complete __init__ method for you based on the type-hinted attributes you define, which means you don't have to write that boilerplate code yourself.
- This simplifies your class definition, making it cleaner and less prone to errors.
- For mutable default values, like a list for
skills, you usefield(default_factory=list). This is a crucial safety feature that ensures each new object gets its own unique list, preventing them from accidentally sharing the same one.
Move faster with Replit
Replit is an AI-powered development platform that transforms natural language into working applications. You can describe what you want to build, and Replit Agent creates it—complete with databases, APIs, and deployment.
The constructor patterns you've learned, from basic __init__ methods to advanced dataclasses, are the building blocks for real software. Replit Agent can take these concepts and turn them into production-ready tools:
- Build a user registration system where the constructor validates new sign-ups for correct email formats and password strength.
- Create a data import tool that uses alternative constructors to create objects from different sources, like a JSON file or a database row.
- Deploy an inventory management app that uses dataclasses to automatically generate constructors for products with default stock levels.
Describe your app idea, and Replit Agent will write the code, test it, and fix issues automatically, all within your browser. Try Replit Agent to bring your next project to life.
Common errors and challenges
Even experienced developers run into a few common pitfalls when working with Python constructors, but they’re easy to avoid once you know what to look for.
Forgetting self in constructor parameters
A frequent mistake is forgetting to include self as the first parameter in the __init__ method. Because Python automatically passes the instance as the first argument, omitting self causes a mismatch between the arguments provided and the parameters defined.
This oversight immediately triggers a TypeError, as Python tries to assign the first argument you provided (e.g., a name) to the parameter that should have been self. Always ensure __init__ starts with def __init__(self, ...).
Mutable default arguments in constructors
Using mutable objects like a list or dictionary as default arguments in a constructor is a classic trap. Python evaluates default arguments only once when the function is defined, not every time it’s called. This means all instances created without that specific argument will share the exact same list or dictionary.
If one object modifies the shared default, the change unexpectedly appears in all other objects. The correct pattern is to set the default to None and then create a new mutable object inside the __init__ method if the argument is None.
Inheritance and forgetting to call parent __init__
When you create a subclass that has its own __init__ method, you must explicitly call the parent class’s constructor. If you don't, the parent's initialization logic never runs, and your object may be missing essential attributes.
Forgetting this step often leads to AttributeError exceptions later when your code tries to access an attribute that was supposed to be set by the parent class. The standard way to ensure proper initialization is to call super().__init__() at the beginning of the child class’s constructor.
Forgetting self in constructor parameters
Forgetting self in an __init__ method is a common slip-up that results in a confusing TypeError. Python expects the instance as the first argument, but without self to catch it, the parameters don't line up. The following code demonstrates this issue.
class Rectangle:
def __init__(width, height):
width = width
height = height
def area(self):
return self.width * self.height
rect = Rectangle(10, 5)
print(f"Area: {rect.area()}")
Since the __init__ method is missing self, the width and height values aren't attached to the instance. The assignments are only local, so the area() method fails. The corrected code below shows how to fix this.
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
rect = Rectangle(10, 5)
print(f"Area: {rect.area()}")
The corrected code works because it includes self as the first parameter in the __init__ method. This allows you to attach the width and height values directly to the object instance using self.width and self.height. Without self, the assignments are only local variables that disappear after the method runs. This simple fix ensures the attributes are stored with the object, making them available to other methods like area().
Mutable default arguments in constructors
It’s a classic mistake to use a mutable default argument like items=[] in a constructor. Because Python creates the default list just once, every instance that relies on it ends up sharing the exact same list, leading to unexpected behavior.
The code below shows what happens when two different TodoList objects unintentionally modify the same list.
class TodoList:
def __init__(self, name, items=[]):
self.name = name
self.items = items
def add_item(self, item):
self.items.append(item)
list1 = TodoList("Work")
list1.add_item("Finish project")
list2 = TodoList("Personal")
list2.add_item("Buy groceries")
print(list1.items)
print(list2.items)
Since both list1 and list2 fall back on the default items argument, they end up sharing the same list. Adding an item to one object unintentionally alters the list for the other. The following example shows the standard pattern for safely initializing mutable attributes.
class TodoList:
def __init__(self, name, items=None):
self.name = name
self.items = items if items is not None else []
def add_item(self, item):
self.items.append(item)
list1 = TodoList("Work")
list1.add_item("Finish project")
list2 = TodoList("Personal")
list2.add_item("Buy groceries")
print(list1.items)
print(list2.items)
The corrected code solves the problem by setting the default for items to None. The constructor then uses the expression items if items is not None else [] to create a brand new empty list for each object that doesn't receive one. This ensures every instance starts with its own unique list, so modifications to one don't accidentally affect another. It's a crucial pattern to remember whenever you use mutable types as default arguments.
Inheritance and forgetting to call parent __init__
In inheritance, if a child class defines its own __init__, it overrides the parent's. You must explicitly call the parent's constructor, or its setup logic won't run. This leaves the object partially uninitialized, often causing errors later on.
The following example shows what happens when the Dog class forgets to initialize its Animal parent, leading to a missing attribute.
class Animal:
def __init__(self, species):
self.species = species
class Dog(Animal):
def __init__(self, name, breed):
self.name = name
self.breed = breed
def info(self):
return f"{self.name} is a {self.species} of breed {self.breed}"
dog = Dog("Rex", "German Shepherd")
print(dog.info())
The Dog class's __init__ method never calls its parent's constructor, so the species attribute is never assigned. This triggers an AttributeError when the info() method tries to access self.species. The corrected implementation follows.
class Animal:
def __init__(self, species):
self.species = species
class Dog(Animal):
def __init__(self, name, breed):
super().__init__("dog")
self.name = name
self.breed = breed
def info(self):
return f"{self.name} is a {self.species} of breed {self.breed}"
dog = Dog("Rex", "German Shepherd")
print(dog.info())
The corrected code calls the parent's constructor using super().__init__("dog") at the start of the child's __init__ method. This simple line runs the Animal class's setup logic, correctly assigning the species attribute. You'll need to do this whenever a subclass defines its own __init__, ensuring the object inherits all necessary attributes from its parent and preventing AttributeError exceptions down the line.
Real-world applications
With those common errors handled, you can see how constructors are used to build practical features, from a simple shopping cart to a singleton.
Creating a shopping cart with __init__
A shopping cart provides a classic example of how the __init__ constructor sets up an object's initial state, giving it a customer ID and an empty list to store items.
class ShoppingCart:
def __init__(self, customer_id):
self.customer_id = customer_id
self.items = []
def add_item(self, product, price, quantity=1):
self.items.append({"product": product, "price": price, "quantity": quantity})
cart = ShoppingCart("cust123")
cart.add_item("Laptop", 1000)
cart.add_item("Mouse", 25, 2)
print(f"Cart: {cart.items}")
When you create a ShoppingCart object, its __init__ method assigns the provided customer_id and prepares an empty items list. This ensures every cart starts fresh and is tied to a specific customer, preventing data from mixing.
- The
add_itemmethod bundles product details into a dictionary before appending it to that specific cart's list. - Notice how the
quantityparameter has a default value, making it optional when you call the method.
This design keeps each cart's contents neatly organized and separate from others.
Implementing a singleton pattern with __new__ and __init__
While __init__ initializes an object, the singleton pattern uses the more fundamental __new__ method to ensure only one instance of a class is ever created.
class DatabaseConnection:
_instance = None
def __new__(cls, host, user):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.host = host
cls._instance.user = user
return cls._instance
db1 = DatabaseConnection("localhost", "user1")
db2 = DatabaseConnection("otherhost", "user2")
print(f"Same instance: {db1 is db2}, Host: {db1.host}")
This code uses the special __new__ method to control object creation itself, a process that runs before __init__. Its goal is to enforce that only one DatabaseConnection object can ever exist in the application.
- The first time you instantiate the class, it creates and returns a new object.
- On any subsequent attempt, it bypasses creation and simply returns the original object.
This pattern ensures all parts of your application share a single, consistent connection point, preventing multiple connections from being opened unnecessarily.
Get started with Replit
Now, put these constructor patterns to work. Try prompting Replit Agent with: “Build a Python class for a simple Stopwatch” or “Create a Character class for a game with stats like health and mana.”
The agent takes your description and writes the code, tests for errors, and handles deployment. Start building with Replit.
Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.
Create & deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.


.png)
.png)