How to create a class in Python
Learn how to create a class in Python. This guide covers different methods, tips, real-world applications, and common error debugging.

Python's class keyword is the foundation of object-oriented programming. Classes bundle data and functions into a single unit, which helps you create organized, reusable, and scalable code for applications.
In this article, you'll explore key techniques and tips. We'll cover real-world applications and debugging advice to help you master Python classes and build more powerful software with confidence.
Basic class definition
class Car:
pass
my_car = Car()
print(type(my_car))--OUTPUT--<class '__main__.Car'>
This example lays the groundwork for creating objects. The class Car: line defines a new type, essentially a blueprint for creating "Car" objects. Inside the class, you'll find:
- The
passstatement: This acts as a placeholder. It allows the code to run without error even though the class is empty. You use it when you've defined a class structure but aren't ready to implement its methods or attributes. - Instantiation: The line
my_car = Car()creates an instance of theCarclass. This is how you create a concrete object from your blueprint.
The output confirms that my_car is now an object of your custom Car type, not a generic Python object. This demonstrates the fundamental process of creating an instance of a class.
Basic class features
To make your empty class useful, you'll add attributes to store data and methods to define behavior, often starting with the __init__ constructor.
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 version, the Car class now has attributes. These are called class attributes—variables shared by all instances of the class. Think of them as default properties that every object will have.
- The variables
colorandwheelsare defined directly inside the class. - Every object created from the
Carclass automatically gets these attributes and their values.
You can then access these shared properties using dot notation, like my_car.color, to retrieve their values.
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.
Now, the Car class has methods, which are functions that define an object's behavior. They're how you make your objects do things. The drive() and stop() functions are now part of the Car blueprint, defining actions any car can perform. This approach works particularly well when AI coding is better with Python.
- The
selfparameter is a reference to the specific instance of the class—in this case,my_car. It's automatically passed when you call a method, giving it access to the object's data. - You call these methods on an instance using dot notation, like
my_car.drive(). This executes the function's code in the context of that specific object.
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 object. It’s the ideal place to set up an object with its own unique data from the very beginning.
- The parameters
colorandmodelare passed when you create the object, as inCar("blue", "sedan"). - Inside
__init__, lines likeself.color = colorcreate instance attributes. This means eachCarobject can have its own distinct properties, unlike the shared class attributes discussed earlier.
Advanced class concepts
Building on the basics of attributes and methods, you can now create more sophisticated class designs with inheritance, properties, and specialized class-level methods.
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 “child” class that adopts the attributes and methods of a “parent” class. Here, Car inherits from Vehicle, which is established with the syntax class Car(Vehicle):. This creates a powerful relationship for reusing code.
- Your
Carobject can use themove()method directly from its parentVehicleclass. - The child class can also have its own unique methods, like
honk(), that are not part of the parent.
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 more control over attributes while keeping the syntax clean. You can access circle.radius as if it's a simple variable, but behind the scenes, Python is calling methods to manage the underlying _radius value.
- The
@propertydecorator creates a "getter," which runs a method when you read an attribute's value. - The
@radius.setterdecorator creates a "setter," which runs when you assign a new value. This is perfect for adding validation logic, like ensuring the radius is always positive.
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 functions that belong to a class but don't operate on a specific instance, so you can call them directly on the class itself. They're useful for creating utility functions that are logically related to the class. The principles of creating functions in Python apply here, with additional decorators for class context.
- A
@staticmethod, likeadd(), is a self-contained function. It doesn't receive the instance (self) or class (cls) as an argument. - A
@classmethod, likemultiply(), receives the class itself as its first argument, conventionally namedcls. This allows the method to work with the class, such as calling other class methods.
Move faster with Replit
Replit is an AI-powered development platform where all Python dependencies come pre-installed, so you can skip setup and start coding instantly. While mastering individual techniques is essential, the real goal is to build working software. Agent 4 helps you bridge that gap, taking an idea to a complete application by handling the code, databases, APIs, and deployment directly from your description.
Instead of piecing together classes and methods manually, you can describe the final product you want:
- A custom data validator that uses properties to ensure all incoming user data meets specific rules before being saved.
- A simple inventory tracker that uses classes to manage different product types, each with unique attributes like price and stock level.
- A basic vehicle management tool where
Carobjects inherit movement logic from a parentVehicleclass but add their own unique methods.
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 the basics, a few common pitfalls can trip you up when creating classes in Python.
Forgetting the self parameter in class methods
This is one of the most frequent errors for newcomers. Every instance method’s first parameter must be self, which is how Python passes a reference to the object into the method. If you forget it, you’ll get a TypeError because the method isn't prepared to receive the instance that Python automatically sends when you call it.
Mixing up instance and class variables
It's easy to confuse variables that belong to an instance with those shared by the entire class. Instance variables, typically set in __init__, hold data unique to each object. Class variables are shared across all instances. The trouble starts when you modify a class variable expecting the change to affect only one object—you'll inadvertently alter the property for every other instance created from that class. This kind of confusion can lead to unexpected memory leaks in larger applications.
Incorrect use of super() in inheritance
The super() function is your tool for calling methods from a parent class, which is essential for making inheritance work correctly. A frequent mistake is forgetting to call super().__init__() inside a child class's constructor. When this happens, the parent class is never properly initialized, and your object may be missing important attributes or setup logic from its parent.
Forgetting the self parameter in class methods
Forgetting the self parameter is a classic mistake that immediately breaks your code. When you define a method that should belong to an object, Python expects self as the first argument. Without it, you'll trigger a TypeError, as the following example shows.
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)
The TypeError occurs because Python automatically passes the calc instance as an argument to add. Since the method was only defined to accept x and y, the call fails. The corrected implementation below accounts for 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 the add method, you correctly define it as an instance method. This signals that the function belongs to an object and is prepared to receive the instance—in this case, calc—that Python automatically passes during the call.
This fix is crucial whenever you write methods intended to be called from an object. Without self, Python can't connect the method to the instance, leading to the error.
Mixing up instance and class variables
It's easy to accidentally modify a class variable when you mean to change an instance variable. This mistake can cause confusing bugs because the change affects every object created from the class. See how this plays out in the code below.
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 issue is that self.count += 1 creates a new instance attribute on c1 instead of changing the shared class variable. This is why c2 still sees the original value. The following code shows the correct implementation.
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)
By initializing self.count = 0 inside the __init__ method, you create an instance variable. This ensures each object gets its own separate state from the moment it’s created. Now, when you call increment() on c1, it only affects its personal counter, leaving c2 untouched. It's the correct approach whenever you need objects to manage their own data independently rather than sharing a single value across the entire class.
Incorrect use of super() in inheritance
The super() function is crucial for calling methods from a parent class, but it's often misused. A common mistake is forgetting to call super().__init__() in a child class's constructor, which leaves the parent class uninitialized and can cause unexpected errors. Master the proper techniques for using super in Python to avoid these pitfalls.
When the parent's __init__ method isn't called, the child object never gets the attributes it's supposed to inherit. The following example shows what happens when the Car class fails to initialize its Vehicle parent, leading to an AttributeError.
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 constructor only sets its model, completely bypassing the parent Vehicle's setup. As a result, the brand attribute is never assigned, causing the program to fail when you try to access it. See the fix below.
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), you ensure the parent Vehicle class runs its own setup logic first. This correctly initializes the brand attribute on your Car object before the child class adds its own properties. Keep an eye out for this whenever you override the __init__ method in a child class—it’s essential for making sure inherited attributes are properly set up and available.
Real-world applications
With the fundamentals covered, you can now use classes to model real-world systems for finance and inventory management. For rapid development of these applications, vibe coding allows you to describe your class structure in natural language and have the code generated automatically.
- Creating a bank account class with
deposit()andwithdraw()methods - Building a simple inventory system with product
inheritance
Creating a bank account class with deposit() and withdraw() methods
The BankAccount class is a practical application that bundles account data with methods like deposit() and withdraw(), which also include validation to check for sufficient funds.
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))
This class manages an account's state through its methods. The __init__ constructor sets up each account with an owner and an optional balance that defaults to 0.
- The
deposit()method modifies the object's state by increasing thebalance. - The
withdraw()method includes conditional logic. It only changes the balance if the requested amount is less than or equal to the current balance, ensuring the account can't be overdrawn.
Both methods return descriptive strings to confirm the transaction's outcome.
Building a simple inventory system with product inheritance
Inheritance allows you to build a flexible inventory system by defining a base Product class and extending it with specialized versions, like a DiscountedProduct that adds its own pricing rules.
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 example shows how the DiscountedProduct class inherits from a base Product class, letting you reuse code while adding new features. The structure is efficient and avoids duplication.
- The child class,
DiscountedProduct, usessuper().__init__()to run the parent's constructor, which sets thenameandprice. - It then adds its own unique attribute,
discount_percent, and a custom method,get_final_price(), to calculate the new cost.
This approach allows you to create specialized objects that build upon a common foundation.
Get started with Replit
Now, turn these concepts into a working tool. Describe what you want to Replit Agent, like “a unit converter class for metric and imperial” or “a budget tracker to log expenses by category.”
Replit Agent then writes the code, tests for errors, and deploys the app directly from your description. Start building with Replit.
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.
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.



