Table of contents
Implement code functionality

How to create a class in Python

May 22, 2025
 ・ by  
Claude and the Anthropic Team
Table of contents
H2 Link Template
Try Claude

Python classes provide a powerful way to create reusable code by bundling data and functionality together. Classes serve as blueprints for objects, enabling you to structure your code efficiently while following object-oriented programming principles.

This guide covers essential techniques for creating robust Python classes, with practical examples and debugging tips created using Claude, an AI assistant built by Anthropic.

Basic class definition

class Car:
    pass

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

The class Car definition demonstrates the minimal structure needed to create a custom class in Python. The pass statement serves as a placeholder, allowing you to establish the basic framework before adding attributes and methods.

When you instantiate the class with my_car = Car(), Python creates a new object based on this blueprint. While this example is intentionally simple, it illustrates two core concepts:

  • Classes don't require initial attributes or methods to be valid
  • Each instance maintains its own identity, as shown by the output indicating it's a unique object of type Car

Basic class features

Building on this foundation, we'll explore how to enhance Python classes with attributes, methods, and the __init__ constructor to create more sophisticated and functional objects.

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.")
My car is red and has 4 wheels.

This example demonstrates class attributes, which belong to the class itself rather than individual instances. The attributes color and wheels are defined directly within the class body, making them accessible to all instances of Car.

  • Class attributes serve as default values that all instances inherit when created
  • You can access these attributes using dot notation (my_car.color) on any instance
  • These attributes exist even before creating any instances of the class

While this approach works for simple cases, instance attributes (defined in __init__) often provide more flexibility for real-world applications. We'll explore those next.

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())
The car is moving!
The car has stopped.

Methods define the behaviors and actions that objects of a class can perform. In this example, the Car class has two methods: drive() and stop(). Each method takes self as its first parameter, which refers to the instance of the class being used.

  • The self parameter enables methods to access and modify the instance's attributes and other methods
  • You call methods using dot notation on class instances (my_car.drive())
  • Methods can return values, perform actions, or both

When you create a new car object with my_car = Car(), it inherits all these defined behaviors. You can then call these methods to make the car move or stop, demonstrating how classes bundle related functionality together.

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}.")
I have a blue sedan.

The __init__ method serves as a constructor that automatically runs when you create a new instance of a class. It enables you to set up initial instance attributes that are unique to each object.

  • The parameters color and model let you customize each car's properties when creating it
  • Using self.color = color creates instance attributes that belong specifically to each car object
  • Instance attributes differ from class attributes because each object can have different values

When you write my_car = Car("blue", "sedan"), Python passes these arguments to __init__ to initialize your new car object with those specific characteristics. This approach provides more flexibility than using fixed class attributes.

Advanced class concepts

Building on these foundational class concepts, Python offers powerful mechanisms to extend and refine your classes through inheritance, property decorators, and specialized method types.

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
Moving...
Beep beep!

Inheritance enables a class to acquire properties and methods from another class. In this example, Car inherits from Vehicle using the syntax class Car(Vehicle), establishing Vehicle as the parent class and Car as the child class.

  • The Car class automatically gains access to the move() method from Vehicle
  • Child classes can define their own unique methods. Here, honk() exists only in Car
  • When you create a Car instance, you can use both inherited and class-specific methods seamlessly

This hierarchical relationship promotes code reuse and logical organization. Common functionality lives in the parent class while specialized behaviors reside in child classes.

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}")
Radius: 5
New radius: 10

Properties provide controlled access to class attributes through getter and setter methods. The @property decorator transforms the radius method into a getter that retrieves the private _radius attribute. The @radius.setter decorator enables you to modify this value with built-in validation.

  • The underscore prefix in _radius signals that this attribute should be treated as private
  • You can access the radius like a regular attribute (circle.radius) instead of calling methods directly
  • The setter validates input by only accepting positive values for the radius

This pattern helps maintain data integrity while providing a clean, intuitive interface for working with class attributes. Properties strike an elegant balance between direct attribute access and the control of getter/setter methods.

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)}")
5 + 3 = 8
5 * 3 = 15

Static and class methods provide ways to associate functions with a class without requiring an instance. The @staticmethod decorator creates utility functions that don't need access to class or instance attributes. The @classmethod decorator enables methods that can access and modify class state through the cls parameter.

  • Static methods like add() work as standalone functions. They don't receive any automatic first parameter
  • Class methods like multiply() automatically receive the class itself as the first parameter (cls)
  • You can call both types directly on the class (MathOperations.add(5, 3)) without creating an instance

These methods help organize code logically within a class structure while maintaining clean separation from instance-specific behaviors. They're particularly useful for operations that conceptually belong to a class but don't need instance data.

Get unstuck faster with Claude

Claude is an AI assistant created by Anthropic that excels at helping developers write, understand, and debug Python code. It combines deep programming knowledge with natural conversation to provide clear, actionable guidance.

When you encounter tricky class inheritance scenarios or need help implementing properties, Claude can analyze your code and explain solutions step by step. It helps you understand not just what to fix but why certain approaches work better than others.

Start accelerating your Python development today. Sign up for free at Claude.ai to get personalized help with class design, debugging, and best practices implementation.

Some real-world applications

Building on these foundational concepts, we'll examine two practical examples that demonstrate how Python classes solve common business challenges: managing financial transactions and organizing product data.

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

The BankAccount class demonstrates how to model financial transactions by tracking an account owner's balance and providing secure methods for deposits and withdrawals.

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 implements core banking operations through a clean, object-oriented design. The constructor takes an owner's name and an optional starting balance, storing them as instance attributes.

  • The deposit() method adds funds and returns a confirmation message with the new balance
  • The withdraw() method includes a safety check to prevent overdrafts
  • String formatting with f-strings provides clear transaction feedback

The example code creates an account for Alice with $100, performs a $50 deposit and a $25 withdrawal successfully. The final withdrawal attempt of $200 fails due to insufficient funds, demonstrating the built-in account protection.

Building a simple inventory system with product inheritance

The Product and DiscountedProduct classes demonstrate inheritance in action by modeling a flexible inventory system that handles both regular and discounted items through a shared base class structure.

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 class inheritance in action. The base Product class defines core attributes like name and price. The DiscountedProduct class extends this functionality by inheriting from Product and adding discount calculations.

  • The super().__init__() call ensures proper initialization of parent class attributes
  • The get_final_price() method calculates the discounted amount using a percentage reduction formula

The example creates two products: a regular laptop at full price and headphones with a 20% discount. The DiscountedProduct maintains all the basic product features while adding specialized pricing behavior. This pattern enables flexible handling of different product types through a unified interface.

Common errors and challenges

Understanding these common Python class pitfalls will help you write more reliable code and avoid frustrating debugging sessions.

Forgetting the self parameter in class methods

One of the most frequent mistakes when writing Python class methods involves omitting the self parameter. This critical error occurs when developers define instance methods without including self as the first parameter. The following code demonstrates this common pitfall.

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 Python calls the add method, it automatically passes the instance as the first argument. Without self, Python can't bind the method to the instance, causing a TypeError. Let's examine the corrected version below.

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)

The corrected version properly includes self as the first parameter in the add method. This enables Python to automatically pass the instance reference when calling instance methods. Without self, Python raises a TypeError because it can't bind the method to the instance object.

  • Always include self as the first parameter for instance methods
  • Static and class methods are exceptions that use different decorators
  • Your IDE can help catch missing self parameters before runtime

This error commonly surfaces when converting standalone functions into class methods. Double-check method signatures during refactoring to ensure proper instance binding.

Mixing up instance and class variables

Developers often confuse class variables (shared across all instances) with instance variables (unique to each object). This distinction becomes critical when modifying values, as class variables can produce unexpected behavior. The code below demonstrates a common pitfall with the count variable.

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 increment() method attempts to modify count using self.count. This creates a new instance variable instead of updating the shared class variable. The following code demonstrates the proper 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)

The corrected code initializes count as an instance variable in __init__ instead of defining it as a class variable. This ensures each Counter object maintains its own independent count. When you increment c1's count, c2's count remains unchanged—exactly what you'd expect for separate counters.

  • Watch for mutable class variables like lists or dictionaries. They can cause unexpected behavior when modified
  • Use instance variables for data that should be unique to each object
  • Reserve class variables for truly shared values that all instances should reference

Incorrect use of super() in inheritance

Failing to properly call super().__init__() in child classes creates a common inheritance trap. When a child class overrides the parent's __init__ method without invoking the parent initialization, it breaks the inheritance chain and prevents access to parent attributes.

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 fails to initialize its parent class attributes by skipping super().__init__(). This prevents the brand attribute from being set during object creation. The code below demonstrates the proper implementation with inheritance.

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"

The corrected code properly initializes parent class attributes by calling super().__init__() in the child class constructor. This ensures the brand attribute gets set during object creation. Without this call, the child class would lose access to parent class attributes.

  • Always call super().__init__() when overriding parent class constructors
  • Place the super() call before initializing child-specific attributes
  • Watch for missing parent initialization when debugging attribute access errors

This pattern becomes especially important in complex inheritance hierarchies with multiple parent classes. Modern IDEs can help catch missing super() calls during development.

Learning or leveling up? Use Claude

Claude combines advanced programming expertise with intuitive teaching abilities to guide you through Python class development. This AI assistant from Anthropic excels at explaining complex concepts clearly while helping you implement robust, maintainable code.

Here are some example prompts to get personalized help with Python classes:

  • Debug inheritance: Ask "Why isn't my child class accessing the parent's attributes?" and Claude will help identify missing super() calls or initialization issues
  • Design patterns: Ask "What's the best way to implement a singleton pattern in Python?" and Claude will explain the implementation with practical examples
  • Property usage: Ask "When should I use properties instead of regular attributes?" and Claude will clarify the benefits and trade-offs of each approach
  • Method types: Ask "What's the difference between static, class, and instance methods?" and Claude will demonstrate when to use each type
  • Best practices: Ask "How can I make my class more maintainable?" and Claude will suggest improvements for better code organization

Experience personalized Python guidance by signing up for free at Claude.ai.

For a more integrated development experience, Claude Code brings AI assistance directly to your terminal, enabling seamless collaboration while you write and refine your Python classes.

FAQs

Additional Resources

How to convert an int to a string in Python

2025-05-30
14 min
 read
Read more

How to use pi in Python

2025-05-30
14 min
 read
Read more

How to find the length of a list in Python

2025-05-30
14 min
 read
Read more

Leading companies build with Claude

ReplitCognitionGithub CopilotCursorSourcegraph
Try Claude
Get API Access
Copy
Expand