How to create a class in Python

Learn how to create a class in Python. In this guide, you'll find methods, tips, real-world applications, and how to debug common errors.

How to create a class in Python
Published on: 
Fri
Feb 6, 2026
Updated on: 
Tue
Feb 10, 2026
The Replit Team Logo Image
The Replit Team

A Python class acts as a blueprint for objects, a core concept in object-oriented programming. It lets you structure code into reusable components for more organized and scalable applications.

You'll learn the syntax to define a class, explore real-world applications, and get tips to debug common errors. This foundation will help you write more efficient code.

Basic class definition

class Car:
pass

my_car = Car()
print(type(my_car))--OUTPUT--<class '__main__.Car'>

This example shows the simplest possible class definition. The class Car: line declares the blueprint, and the pass statement acts as a placeholder. Since Python syntax requires an indented block for the class body, pass allows you to define the class without adding any attributes or methods yet.

The line my_car = Car() creates an *instance* of the class—a specific object built from the Car blueprint. Finally, checking its type() confirms that the my_car variable now holds a Car object, not just a generic piece of data. This is the fundamental step of bringing a class definition to life.

Basic class features

An empty class is just the start; you bring it to life by defining attributes, methods, and an __init__ constructor to initialize its state.

Class with attributes

class Car:
color = "red"
wheels = 4

my_car = Car()
print(f"My car is {my_car.color} and has {my_car.wheels} wheels.")--OUTPUT--My car is red and has 4 wheels.

In this example, color and wheels are class attributes. They are variables defined directly inside the Car class, making them shared properties for every object created from that blueprint.

  • Any new Car instance will automatically have these default values.
  • You can access them using dot notation, like my_car.color.

This is a straightforward way to define characteristics that are consistent across all instances of a class.

Class with methods

class Car:
def drive(self):
return "The car is moving!"

def stop(self):
return "The car has stopped."

my_car = Car()
print(my_car.drive())
print(my_car.stop())--OUTPUT--The car is moving!
The car has stopped.

Methods are functions defined inside a class that describe its behaviors. Here, drive() and stop() are methods that define what a Car object can do. You call them on an instance, like my_car.drive(), to execute their logic.

  • The first parameter, self, is a reference to the instance itself. It’s how the method knows which object it's working with.
  • Python automatically passes the instance to the self parameter when you call the method, so you don't need to provide it explicitly.

Using the __init__ constructor

class Car:
def __init__(self, color, model):
self.color = color
self.model = model

my_car = Car("blue", "sedan")
print(f"I have a {my_car.color} {my_car.model}.")--OUTPUT--I have a blue sedan.

The __init__ method is a special constructor that Python calls automatically when you create a new instance. It’s the ideal place to set up an object’s initial state with unique attributes, rather than using shared class attributes.

  • When you create an instance like Car("blue", "sedan"), the arguments are passed directly to __init__.
  • The self keyword refers to the instance itself, allowing you to assign values to it—for example, self.color = color. This makes the attributes specific to that one object.

This way, each Car you create can have its own distinct properties right from the start.

Advanced class concepts

With the basics down, you can now build on your class blueprints using advanced features to create specialized objects and manage their behavior more precisely.

Class inheritance

class Vehicle:
def move(self):
return "Moving..."

class Car(Vehicle):
def honk(self):
return "Beep beep!"

my_car = Car()
print(my_car.move()) # Inherited method
print(my_car.honk()) # Car-specific method--OUTPUT--Moving...
Beep beep!

Inheritance lets you create a specialized class that builds upon a more general one. In this example, the Car class inherits from Vehicle, which means it automatically gets all of Vehicle's capabilities. This creates an "is-a" relationship—a Car is a type of Vehicle—and helps you reuse code efficiently.

  • The Car instance can call move() because the method is inherited from the Vehicle parent class.
  • It can also have its own unique methods, like honk(), to define behaviors specific to a Car.

Using properties with getters and setters

class Circle:
def __init__(self, radius):
self._radius = radius

@property
def radius(self):
return self._radius

@radius.setter
def radius(self, value):
if value > 0:
self._radius = value

circle = Circle(5)
print(f"Radius: {circle.radius}")
circle.radius = 10
print(f"New radius: {circle.radius}")--OUTPUT--Radius: 5
New radius: 10

Properties give you a Pythonic way to manage attribute access, making your code cleaner and safer. The attribute is stored internally as _radius, but you interact with it through a public-facing property.

  • The @property decorator turns a method into a "getter," so you can read circle.radius like a simple attribute.
  • The @radius.setter decorator creates a "setter" that intercepts assignments. This lets you add validation logic—like checking if the new value is positive—before updating the internal _radius.

Static and class methods

class MathOperations:
@staticmethod
def add(x, y):
return x + y

@classmethod
def multiply(cls, x, y):
return x * y

print(f"5 + 3 = {MathOperations.add(5, 3)}")
print(f"5 * 3 = {MathOperations.multiply(5, 3)}")--OUTPUT--5 + 3 = 8
5 * 3 = 15

Static and class methods are called directly on the class, not an instance, making them useful for utility functions. You don't need to create an object like MathOperations() to use them.

  • A @staticmethod like add() is self-contained. It doesn't receive the class or an instance as an argument, so it behaves like a regular function that's just namespaced inside the class.
  • A @classmethod like multiply() receives the class itself as its first argument, conventionally named cls. This allows the method to access or modify class-level properties or call other class methods.

Move faster with Replit

Replit is an AI-powered development platform that transforms natural language into working applications. Describe what you want to build, and Replit Agent creates it—complete with databases, APIs, and deployment.

For the class concepts we've covered, Replit Agent can turn them into production-ready tools:

  • Build a fleet management tool where different vehicle types inherit from a base Vehicle class, each with unique attributes and methods.
  • Create a user profile system where properties validate inputs like email addresses or age, ensuring data integrity.
  • Deploy a unit conversion utility with static methods for converting measurements, all organized within a single class.

Describe your app idea, and Replit Agent writes the code, tests it, and fixes issues automatically. Try Replit Agent to turn your concepts into working applications.

Common errors and challenges

Working with classes can introduce some tricky errors, but understanding the common pitfalls makes them much easier to debug and avoid.

Forgetting the self parameter in class methods

One of the most frequent mistakes is forgetting to include self as the first parameter in an instance method. When you call a method like my_car.drive(), Python automatically passes the my_car instance as the first argument behind the scenes.

If your method is defined as def drive(): without self, Python will raise a TypeError because the method wasn't expecting to receive the instance. The fix is simple—always define instance methods with self as the first parameter, like def drive(self):, to correctly capture the object instance.

Mixing up instance and class variables

It's easy to confuse instance variables (unique to each object) with class variables (shared by all objects). This distinction is crucial, especially when working with mutable types like lists or dictionaries.

  • Instance variables should be defined inside the __init__ method using self (e.g., self.color = "blue"). They hold data specific to one object.
  • Class variables are defined directly within the class. If you modify a mutable class variable through one instance, the change will affect all other instances of that class.

To avoid unexpected side effects, use instance variables for any data that needs to be unique to an object. This ensures that changes to one object don't accidentally impact others.

Incorrect use of super() in inheritance

When you create a child class that inherits from a parent, using super() correctly is essential for proper initialization. The super() function lets you call methods from the parent class, most often the __init__ constructor.

A common error is forgetting to call super().__init__() within the child class's __init__ method. If you skip this step, the parent class's setup logic never runs, and your child object may be missing important attributes. This often leads to an AttributeError later when your code tries to access an attribute that was never created.

Always ensure you call super().__init__() in your subclass's constructor, passing along any arguments the parent's constructor needs. This guarantees that the object is fully and correctly built from the ground up.

Forgetting the self parameter in class methods

Forgetting the self parameter in a method definition is a classic error. Python automatically passes the instance as the first argument when you call a method. If the method isn't defined to accept it, a TypeError is raised, as the following code demonstrates.

class Calculator:
def add(x, y): # Missing 'self' parameter
return x + y

calc = Calculator()
result = calc.add(5, 3) # This will cause a TypeError
print(result)

When calc.add(5, 3) is called, Python passes the calc instance, 5, and 3 as arguments. Since the add(x, y) method only expects two, a TypeError occurs. The corrected code below shows how to fix this.

class Calculator:
def add(self, x, y): # Added 'self' parameter
return x + y

calc = Calculator()
result = calc.add(5, 3) # Now works correctly
print(result)

By adding self as the first parameter to def add(self, x, y):, the method can now accept the calc instance that Python passes automatically. This resolves the TypeError because the method signature now matches the arguments being sent—the instance, 5, and 3. You'll typically encounter this error when defining any instance method, so always make sure self is your first parameter.

Mixing up instance and class variables

A common mistake is unintentionally creating a new instance variable when you think you're changing a class-level one. When you assign to an attribute like self.count, you're not updating the shared counter but creating a separate one for that specific instance.

class Counter:
count = 0 # Class variable shared by all instances

def increment(self):
self.count += 1

c1 = Counter()
c2 = Counter()
c1.increment()
print(f"c1: {c1.count}, c2: {c2.count}") # c1: 1, c2: 0 (unexpected)

The line self.count += 1 creates a new count attribute just for the c1 instance, shadowing the shared class variable. That’s why c2.count remains zero. The following code shows how to correctly modify the class variable.

class Counter:
def __init__(self):
self.count = 0 # Instance variable unique to each instance

def increment(self):
self.count += 1

c1 = Counter()
c2 = Counter()
c1.increment()
print(f"c1: {c1.count}, c2: {c2.count}") # c1: 1, c2: 0 (expected)

The solution is to define the variable inside the __init__ method using self.count = 0. This makes count an instance variable, meaning each object gets its own separate copy. Now, when you call increment() on one instance, it only modifies its own count and doesn't affect others. This is the right approach whenever you need an attribute to hold state that is unique to each object, rather than shared across all of them.

Incorrect use of super() in inheritance

When a child class inherits from a parent, it's crucial to initialize the parent's attributes using super().__init__(). If you forget this step, the parent's setup logic never runs, and your object won't be fully built, as the following code demonstrates.

class Vehicle:
def __init__(self, brand):
self.brand = brand

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

my_car = Car("Toyota", "Corolla")
print(f"Brand: {my_car.brand}") # AttributeError: no attribute 'brand'

The Car class's __init__ overrides the parent's constructor but never calls it. As a result, the brand attribute is never created, leading to an AttributeError. The corrected code below shows how to fix this.

class Vehicle:
def __init__(self, brand):
self.brand = brand

class Car(Vehicle):
def __init__(self, brand, model):
super().__init__(brand) # Call parent constructor
self.model = model

my_car = Car("Toyota", "Corolla")
print(f"Brand: {my_car.brand}") # Works correctly: "Toyota"

By calling super().__init__(brand), the Car class executes the parent Vehicle constructor, which correctly initializes the brand attribute. The child class can then safely add its own attributes like model. This ensures the object is fully built from the parent up.

Always use super() when a child class’s __init__ method needs to extend the parent’s logic, not just replace it. This practice is key to avoiding an AttributeError for missing parent attributes.

Real-world applications

Classes are more than just theory; they’re the building blocks for practical applications that solve real-world problems.

  • Creating a bank account: A class can manage funds with deposit() and withdraw() methods, ensuring each transaction is handled correctly.
  • Building an inventory system: Product-specific classes can use inheritance to build on a general item blueprint, keeping code organized and reusable.

Creating a bank account class with deposit() and withdraw() methods

This class models a simple bank account, using deposit() and withdraw() methods to manage the balance and prevent overdrafts.

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

def deposit(self, amount):
self.balance += amount
return f"Deposited ${amount}. New balance: ${self.balance}"

def withdraw(self, amount):
if amount <= self.balance:
self.balance -= amount
return f"Withdrew ${amount}. New balance: ${self.balance}"
return "Insufficient funds!"

account = BankAccount("Alice", 100)
print(account.deposit(50))
print(account.withdraw(25))
print(account.withdraw(200))

The BankAccount class bundles an account's data and actions into a single unit. Its __init__ constructor sets up each instance with a unique owner and balance, ensuring every account object manages its own state independently. The methods define the account's behavior:

  • The deposit() method directly increases the balance.
  • The withdraw() method adds a layer of protection by first checking if amount <= self.balance, which prevents the balance from becoming negative.

Building a simple inventory system with product inheritance

Inheritance is ideal for an inventory system, as it lets you create a specialized DiscountedProduct class that builds on a general Product blueprint without duplicating code.

class Product:
def __init__(self, name, price):
self.name = name
self.price = price

class DiscountedProduct(Product):
def __init__(self, name, price, discount_percent):
super().__init__(name, price)
self.discount_percent = discount_percent

def get_final_price(self):
return self.price * (1 - self.discount_percent / 100)

regular_product = Product("Laptop", 1000)
print(f"{regular_product.name}: ${regular_product.price}")
discounted_product = DiscountedProduct("Headphones", 100, 20)
print(f"{discounted_product.name}: ${discounted_product.get_final_price()}")

This code demonstrates how a child class extends a parent. The DiscountedProduct class inherits from Product, gaining its core attributes.

  • The super().__init__() call ensures the parent's constructor runs first, setting up the name and price.
  • DiscountedProduct then adds its own unique attribute, discount_percent, and a new method, get_final_price(), to calculate the final cost.

This structure allows the child class to specialize the parent's behavior by adding new data and functionality, creating a more specific type of object.

Get started with Replit

Put your knowledge into practice by building a real tool. Give Replit Agent a prompt like “create a currency converter class” or “build a simple user profile system with data validation.”

Replit Agent writes the code, tests for errors, and deploys your app. Start building with Replit.

Get started free

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.

Get started for free

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.