How to instantiate a class in Python

Learn to instantiate a class in Python. Explore methods, tips, real-world applications, and how to debug common errors.

How to instantiate a class in Python
Published on: 
Fri
Feb 20, 2026
Updated on: 
Mon
Apr 6, 2026
The Replit Team

Instantiating a class in Python is a core concept for any developer using object-oriented programming. It is the process of creating unique, usable objects from a class blueprint.

In this article, we'll cover several techniques for instantiation, with practical tips and real-world applications. You'll also find debugging advice to help you confidently create objects from your classes.

Basic class instantiation with the () operator

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

person = Person("Alice")
print(person.name)--OUTPUT--Alice

The most direct way to create an object is by using the class name followed by parentheses (). This syntax isn't just for show; it's what actually calls the class's special initializer method, __init__. Think of it as the factory switch that starts the assembly line for a new object, similar to the initial steps involved in creating a class in Python.

When you run person = Person("Alice"), Python first creates an empty object. It then passes that new object as the first argument, self, to the __init__ method. This is how the method can attach data like name to the specific instance being created.

Common instantiation patterns

Beyond that simple call, the real power of instantiation comes from how you work with constructor parameters to build flexible and descriptive objects.

Using constructor parameters

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

rect = Rectangle(5, 10)
print(f"Width: {rect.width}, Height: {rect.height}, Area: {rect.area}")--OUTPUT--Width: 5, Height: 10, Area: 50

The __init__ method is your chance to define exactly what an object needs to exist. In this case, a Rectangle requires a width and a height. The constructor isn't just for storing data; it can also perform operations. Here, it calculates the area right away, making the object fully formed from the start. For more details on creating constructors in Python, you can explore additional patterns.

  • When you call Rectangle(5, 10), you're not just passing values.
  • You're triggering a process where the object's width, height, and calculated area are all set in one go.

Creating instances with default parameter values

class Counter:
def __init__(self, start=0, step=1):
self.value = start
self.step = step

def increment(self):
self.value += self.step

counter = Counter(10, 2)
counter.increment()
print(counter.value)--OUTPUT--12

Default parameters in the __init__ method offer flexibility when creating objects. By setting start=0 and step=1, you're providing fallback values. This means you don't always have to supply every argument during instantiation.

  • Counter() would create a counter starting at 0 that increments by 1.
  • Counter(5) would start at 5 and still increment by 1.
  • The example Counter(10, 2) overrides both defaults, creating a counter that starts at 10 and increments by 2.

Using named arguments for clarity

class User:
def __init__(self, username, email, active=True):
self.username = username
self.email = email
self.active = active

user = User(username="john_doe", email="john@example.com", active=False)
print(f"User: {user.username}, Status: {'Active' if user.active else 'Inactive'}")--OUTPUT--User: john_doe, Status: Inactive

Named arguments, or keyword arguments, boost your code's readability. By explicitly naming each parameter like username="john_doe", you make it obvious what each value is for. This is a lifesaver when your __init__ method has several parameters, as you don't have to remember their exact order.

  • The order of arguments no longer matters, giving you more flexibility when calling the constructor.
  • You can selectively override default values—like setting active=False in the example—without affecting other parameters.

Advanced instantiation techniques

While the standard __init__ method handles most cases, Python also provides advanced techniques for when you need more control over object creation.

Implementing class methods as alternative constructors

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)

date = Date.from_string("2023-11-15")
print(f"{date.day}/{date.month}/{date.year}")--OUTPUT--15/11/2023

Sometimes you need more than one way to create an object. That's where class methods, marked with the @classmethod decorator, come in. They act as alternative constructors. Instead of receiving the instance (self), they get the class itself (cls) as the first argument, allowing them to call the primary constructor.

  • In the Date example, from_string is a factory for creating objects from a formatted string, which is more descriptive than passing three numbers.
  • It processes the input string and then calls the main constructor using cls(year, month, day) to return a fully formed instance.

Using __new__ for custom instance creation

class Singleton:
_instance = None

def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance

a = Singleton()
b = Singleton()
print(f"Are a and b the same instance? {a is b}")--OUTPUT--Are a and b the same instance? True

The special method __new__ is the true first step in creating an object, running even before __init__. It's what actually constructs and returns the instance, giving you fine-grained control over the process.

  • This is perfect for implementing patterns like the Singleton, which guarantees only one instance of a class ever exists.
  • The method checks if an instance is stored in _instance. If not, it creates one with super().__new__(cls); otherwise, it just returns the existing object.

That's why both variables a and b in the example end up pointing to the exact same instance.

Creating instances with type() dynamically

# Creating a class dynamically
Person = type('Person', (), {'name': 'Anonymous', 'greet': lambda self: f"Hello, I'm {self.name}"})

# Instantiating the dynamically created class
person = Person()
person.name = "Bob"
print(person.greet())--OUTPUT--Hello, I'm Bob

The type() function isn't just for checking an object's type; it can also construct classes dynamically. This is a powerful feature for metaprogramming, allowing your code to define new classes at runtime instead of using the static class keyword.

  • The first argument, 'Person', sets the new class's name.
  • The second argument, (), is a tuple for base classes, which is empty here.
  • The third is a dictionary that maps names to attributes and methods, like 'name' and the 'greet' lambda function.

Once created, you can instantiate this dynamic class with Person() and interact with its objects just as you would with any conventionally defined class.

Move faster with Replit

Replit is an AI-powered development platform where you can apply these techniques immediately. It comes with all Python dependencies pre-installed, so you can skip setup and start coding instantly. To build even faster, describe what you want to build, and Agent 4 will handle everything from writing the code to deployment.

Instead of piecing together techniques, you can describe the app you want to build and let the Agent take it from an idea to a working product:

  • A date utility that creates date objects from various string formats like "YYYY-MM-DD" or "MM/DD/YY".
  • A configuration manager that generates settings objects with sensible defaults, while allowing you to override specific parameters like a database host or port.
  • A dynamic data parser that builds custom classes on the fly to handle different structures from API responses.

Simply describe your app, and Replit will write the code, test it, and fix issues automatically, all within your browser.

Common errors and challenges

Even with a solid grasp of instantiation, you'll sometimes face tricky errors involving self, default arguments, or subclassing.

Debugging missing self parameter in methods

A common TypeError arises when a method is defined without the required self parameter. Python automatically passes the instance as the first argument when you call a method, so its definition must include self to receive it. This mismatch causes the error.

The code below demonstrates this issue with a Counter class. Notice how the increment method is missing self, which triggers the error when you try to run it.

class Counter:
def __init__(self):
self.count = 0

def increment(): # Missing 'self' parameter
self.count += 1

counter = Counter()
counter.increment() # This will raise TypeError

When you call counter.increment(), Python automatically sends the counter instance as an argument. Because the increment() method was defined to accept zero arguments, this mismatch triggers a TypeError. The corrected version below shows how to fix this.

class Counter:
def __init__(self):
self.count = 0

def increment(self): # Added 'self' parameter
self.count += 1

counter = Counter()
counter.increment()
print(counter.count) # Outputs: 1

The fix is simple: adding the self parameter to the method definition, making it def increment(self):. This allows the method to correctly accept the instance Python passes automatically during a call like counter.increment(). With self available, the method can access and modify instance-specific data, such as self.count. For a deeper understanding of using self in Python, you can explore its various applications.

This TypeError often appears when you're first working with classes or refactoring functions into a class structure. It's a good first thing to check.

Fixing issues with mutable default arguments

Fixing issues with mutable default arguments

Using a mutable type like a list as a default argument in your __init__ method is a classic Python pitfall. The default object is created only once, so all instances that rely on it will unexpectedly share the same list.

This shared state can cause confusing bugs when you expect each object to be independent. The code below shows this problem in action.

class ShoppingCart:
def __init__(self, items=[]): # Mutable default argument
self.items = items

def add_item(self, item):
self.items.append(item)

cart1 = ShoppingCart()
cart1.add_item("book")
cart2 = ShoppingCart() # Expected empty cart
print(cart2.items) # Unexpectedly contains "book"

Because the default items=[] is created only once, both cart1 and cart2 receive the same list. When cart1 adds an item, it modifies this shared list, which is why cart2 unexpectedly contains the new item.

The fix ensures each instance gets its own fresh list. The corrected code below shows how to implement this properly.

class ShoppingCart:
def __init__(self, items=None): # None as default
self.items = items if items is not None else []

def add_item(self, item):
self.items.append(item)

cart1 = ShoppingCart()
cart1.add_item("book")
cart2 = ShoppingCart()
print(cart2.items) # Correctly outputs an empty list

The solution is to use None as a safe, immutable default in the method signature. Inside __init__, you then check if the argument is None. If it is, you create a new empty list [] for that specific instance. This pattern ensures each object starts with its own unique list, avoiding unintended side effects. Keep an eye out for this whenever a function's default argument is a list or dictionary.

Resolving attribute access errors in subclasses

When a subclass inherits from a parent, it's easy to forget a crucial step: calling the parent's initializer with super().__init__(). This oversight leads to an AttributeError because the subclass instance never receives the parent's attributes. The code below shows this mistake.

class Vehicle:
def __init__(self, make, model):
self.make = make
self.model = model

class Car(Vehicle):
def __init__(self, make, model, year):
# Missing super().__init__ call
self.year = year

def get_info(self):
return f"{self.make} {self.model} ({self.year})"

car = Car("Toyota", "Corolla", 2020)
print(car.get_info()) # Will raise AttributeError

The Car class’s __init__ method never calls its parent’s initializer, so attributes like self.make are never created. When get_info() tries to access them, it triggers an AttributeError. The corrected code below shows how to fix this.

class Vehicle:
def __init__(self, make, model):
self.make = make
self.model = model

class Car(Vehicle):
def __init__(self, make, model, year):
super().__init__(make, model) # Call parent's __init__
self.year = year

def get_info(self):
return f"{self.make} {self.model} ({self.year})"

car = Car("Toyota", "Corolla", 2020)
print(car.get_info()) # Works correctly

The fix is to explicitly call the parent's initializer with super().__init__(make, model). This step is crucial because it runs the Vehicle class's __init__ method, which sets up the make and model attributes. Without it, the Car instance won't have those attributes, causing an AttributeError when get_info() is called. Keep an eye out for this whenever you're defining an __init__ method in a subclass. To learn more about using super in Python, explore its advanced features.

Real-world applications

With a solid grasp on debugging, you can apply these instantiation techniques to build practical tools for everyday tasks like logging and caching.

Building a simple logger with debug_logger() constructor

Using a class method like debug_logger() offers a clean way to instantiate a logger that's already configured for debugging, bypassing the need to specify its level manually.

class Logger:
def __init__(self, name, level="INFO"):
self.name = name
self.level = level

@classmethod
def debug_logger(cls, name):
return cls(name, "DEBUG")

def log(self, message):
print(f"[{self.level}] {self.name}: {message}")

app_logger = Logger("App")
debug_logger = Logger.debug_logger("DebugMode")
app_logger.log("Application started")
debug_logger.log("Detailed debugging information")

This Logger class provides two distinct ways to create an instance. The standard constructor, __init__, sets up a logger with a default "INFO" level unless you specify otherwise. The debug_logger method, decorated with @classmethod, acts as a specialized factory.

  • It simplifies creating a logger specifically for debugging.
  • It calls the main constructor internally, hardcoding the level to "DEBUG".

This pattern lets you create pre-configured objects for common use cases, making your code cleaner and more descriptive. You can extend this concept further by creating log files in Python to persist logger output.

Implementing a cache with automatic instance tracking

By overriding the __new__ method, you can create a simple caching system that automatically tracks and reuses instances based on a unique key.

class CacheItem:
_instances = {}

def __new__(cls, key, value):
if key in cls._instances:
return cls._instances[key]
instance = super().__new__(cls)
cls._instances[key] = instance
return instance

def __init__(self, key, value):
self.key = key
self.value = value

item1 = CacheItem("user_1", "Alice")
item2 = CacheItem("user_1", "Bob") # Reuses existing instance
print(f"item1.value: {item1.value}")
print(f"item2.value: {item2.value}")
print(f"Same instance? {item1 is item2}")

This class customizes object creation with the __new__ method to ensure only one instance exists per key. It uses a class-level dictionary, _instances, to store and retrieve objects based on their key.

  • When you call CacheItem("user_1", "Alice"), it checks if "user_1" is in the cache. Since it's not, a new object is created and stored.
  • The second call, CacheItem("user_1", "Bob"), finds the existing object and returns it instead of creating a new one.

Because both variables point to the same instance, the __init__ method's second run overwrites the value. That's why both item1.value and item2.value end up as "Bob".

Get started with Replit

Put these instantiation patterns to work. Describe your tool to Replit Agent, like "a unit converter class with methods for metric and imperial" or "a settings manager that loads configuration from a YAML file."

It will write the code, test for errors, and deploy your app directly from your browser. Start building with Replit.

Build your first app today

Describe what you want to build, and Replit Agent writes the code, handles the infrastructure, and ships it live. Go from idea to real product, all in your browser.

Build your first app today

Describe what you want to build, and Replit Agent writes the code, handles the infrastructure, and ships it live. Go from idea to real product, all in your browser.