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.

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
Carinstance 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
selfparameter 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
selfkeyword 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
Carinstance can callmove()because the method is inherited from theVehicleparent class. - It can also have its own unique methods, like
honk(), to define behaviors specific to aCar.
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
@propertydecorator turns a method into a "getter," so you can readcircle.radiuslike a simple attribute. - The
@radius.setterdecorator 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
@staticmethodlikeadd()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
@classmethodlikemultiply()receives the class itself as its first argument, conventionally namedcls. 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
Vehicleclass, 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 usingself(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()andwithdraw()methods, ensuring each transaction is handled correctly. - Building an inventory system: Product-specific classes can use
inheritanceto 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 thebalance. - The
withdraw()method adds a layer of protection by first checking ifamount <= 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 thenameandprice. DiscountedProductthen 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.
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.



