0%

36- Writing Your First Classes

Create your own blueprints. Define attributes and methods. Build objects that model real-world things. Your first step into object-oriented design.

You understand what classes and objects are. Now it is time to write your own. Creating a class is like designing a blueprint. You decide what data it holds (attributes) and what actions it can perform (methods). Then you create objects from that blueprint. This lesson walks you through writing classes from scratch. You will learn the syntax. You will see examples. You will build a complete class step by step. By the end, you will be comfortable creating your own classes for any purpose. Writing a class is not complicated. The syntax is simple. The challenge is thinking in terms of objects. What are the nouns in your problem? Those become classes. What are the adjectives? Those become attributes. What are the verbs? Those become methods.

🕯️ Magic Note

Python’s class syntax is intentionally minimal. There are no access modifiers like public or private (though conventions exist). The philosophy is trust over restriction. Programmers are adults. They will follow conventions. This simplicity makes Python classes easy to write and read.

Class Definition Syntax
Use the class keyword, followed by the class name, a colon, and an indented body. Class names use PascalCase (capitalized words with no underscores).

Python

# Basic class definition

class Car:

pass # Empty class (placeholder)

# Creating an instance (object)

my_car = Car()

print(type(my_car)) # <class ‘__main__.Car’>

💡 Class names should be singular nouns: Car, not Cars. Use PascalCase: BankAccount, UserProfile, ShoppingCart. Follow the convention. Other Python programmers expect it.
Adding Attributes
Attributes are variables that belong to an object. You can add them dynamically, but it is better to define them in the __init__ method.

Python

# Adding attributes dynamically (works but not recommended)

class Car:

pass

my_car = Car()

my_car.brand = “Toyota” # Add attribute on the fly

my_car.year = 2020

print(my_car.brand) # Toyota

your_car = Car()

# print(your_car.brand) # AttributeError! This car has no brand.

⚠️ Adding attributes outside the class is possible but leads to inconsistency. Some instances may have the attribute; others may not. Always define all attributes in __init__ so every object starts with the same attributes.
The __init__ Method
__init__ is the constructor. It runs automatically when you create an object. Use it to initialize attributes.

Python

class Car:

def __init__(self, brand, year, color):

self.brand = brand

self.year = year

self.color = color

self.mileage = 0 # Default value, not passed as parameter

# Creating objects (__init__ is called automatically)

car1 = Car(“Toyota”, 2020, “Red”)

car2 = Car(“Honda”, 2022, “Blue”)

print(car1.brand) # Toyota

print(car1.mileage) # 0

print(car2.color) # Blue

🕯️ Magic Note

The self parameter is automatically passed by Python. It refers to the specific object being created. When you write self.brand = brand, you are storing the value in the object’s instance dictionary.

Adding Instance Methods
Instance methods are functions inside a class that operate on instances. They always take self as the first parameter.

Python

class Car:

def __init__(self, brand, year, color):

self.brand = brand

self.year = year

self.color = color

self.mileage = 0

def drive(self, kilometers):

self.mileage += kilometers

print(f”{self.brand} drove {kilometers} km. Total mileage: {self.mileage}”)

def repaint(self, new_color):

print(f”Changing {self.brand} from {self.color} to {new_color}”)

self.color = new_color

def get_info(self):

return f”{self.brand} ({self.year}) – {self.color} – {self.mileage} km”

# Using the methods

my_car = Car(“Tesla”, 2023, “White”)

my_car.drive(150) # Tesla drove 150 km. Total mileage: 150

my_car.repaint(“Black”) # Changing Tesla from White to Black

print(my_car.get_info()) # Tesla (2023) – Black – 150 km

Class Attributes (Shared Data)
Class attributes belong to the class itself, not to individual instances. All instances share the same class attribute.

Python

class Car:

# Class attribute (shared by all cars)

vehicle_type = “Passenger Vehicle”

total_cars_created = 0

def __init__(self, brand, year, color):

self.brand = brand

self.year = year

self.color = color

self.mileage = 0

Car.total_cars_created += 1 # Increment class attribute

car1 = Car(“Toyota”, 2020, “Red”)

car2 = Car(“Honda”, 2021, “Blue”)

car3 = Car(“Ford”, 2022, “Green”)

# Access class attribute via class or instance

print(Car.vehicle_type) # Passenger Vehicle

print(car1.vehicle_type) # Passenger Vehicle (inherited)

print(Car.total_cars_created) # 3

💡 Use class attributes for constants and data shared across all instances. Use instance attributes for data unique to each object.
Class Methods vs Instance Methods
Class methods operate on the class itself, not on instances. Use the @classmethod decorator. The first parameter is cls (the class), not self.

Python

class Car:

total_cars = 0

def __init__(self, brand):

self.brand = brand

Car.total_cars += 1

# Instance method (operates on a specific car)

def display(self):

print(f”This car is a {self.brand}”)

# Class method (operates on the class)

@classmethod

def get_total_cars(cls):

return f”Total cars created: {cls.total_cars}”

# Class method as alternative constructor

@classmethod

def from_string(cls, car_string):

brand = car_string.split(“-“)[0]

return cls(brand) # Creates a new Car instance

# Using class method

print(Car.get_total_cars()) # Total cars created: 0

car1 = Car(“Toyota”)

car2 = Car(“Honda”)

print(Car.get_total_cars()) # Total cars created: 2

# Alternative constructor

car3 = Car.from_string(“Tesla-2023-Electric”)

print(car3.brand) # Tesla

🕯️ Magic Note

Class methods are useful for alternative constructors and for operations that involve the class as a whole, not individual instances. They are called on the class (Car.get_total_cars()) not on instances.

Static Methods
Static methods do not receive self or cls. They are just functions that belong to the class namespace. Use the @staticmethod decorator.

Python

class MathUtils:

@staticmethod

def is_even(number):

return number % 2 == 0

@staticmethod

def is_prime(number):

if number < 2:

return False

for i in range(2, int(number ** 0.5) + 1):

if number % i == 0:

return False

return True

# Call static methods on the class (no instance needed)

print(MathUtils.is_even(10)) # True

print(MathUtils.is_prime(17)) # True

# In a more relevant example

class Car:

@staticmethod

def validate_year(year):

return 1886 <= year <= 2025 # First car invented in 1886

def __init__(self, brand, year):

if not Car.validate_year(year):

raise ValueError(f”Invalid year: {year}”)

self.brand = brand

self.year = year

💡 Use static methods when the function logic does not depend on instance or class state. They are like regular functions but grouped inside the class for organization.
The __str__ and __repr__ Methods
These are special “dunder” (double underscore) methods that control how objects are displayed as strings.

Python

class Car:

def __init__(self, brand, year, color):

self.brand = brand

self.year = year

self.color = color

# __str__: user-friendly string (used by print())

def __str__(self):

return f”{self.color} {self.brand} from {self.year}”

# __repr__: developer-friendly string (used by debugger)

def __repr__(self):

return f”Car(brand='{self.brand}’, year={self.year}, color='{self.color}’)”

my_car = Car(“Tesla”, 2023, “White”)

print(my_car) # White Tesla from 2023 (uses __str__)

print(repr(my_car)) # Car(brand=’Tesla’, year=2023, color=’White’) (uses __repr__)

🕯️ Magic Note

If you define only __repr__, it will be used for both str() and repr() as a fallback. But it is good practice to define both, especially for classes used in debugging.

Step-by-Step: Building a Complete Class
Let us build a practical class from scratch: a LibraryBook class.

Python

class LibraryBook:

# Class attribute

library_name = “Feloriya Public Library”

def __init__(self, title, author, isbn):

# Instance attributes

self.title = title

self.author = author

self.isbn = isbn

self.is_checked_out = False

self.checkout_history = []

def check_out(self, borrower_name):

if self.is_checked_out:

return False, f”‘{self.title}’ is already checked out”

self.is_checked_out = True

self.checkout_history.append(borrower_name)

return True, f”{borrower_name} checked out ‘{self.title}'”

def return_book(self):

if not self.is_checked_out:

return False, f”‘{self.title}’ was not checked out”

self.is_checked_out = False

return True, f”‘{self.title}’ has been returned”

def get_info(self):

status = “Checked Out” if self.is_checked_out else “Available”

return f”‘{self.title}’ by {self.author} – {status}”

@classmethod

def change_library_name(cls, new_name):

old_name = cls.library_name

cls.library_name = new_name

return f”Library renamed from ‘{old_name}’ to ‘{new_name}'”

@staticmethod

def validate_isbn(isbn):

# Simple validation: ISBN should be 10 or 13 digits

return len(isbn) in (10, 13) and isbn.isdigit()

def __str__(self):

return self.get_info()

def __repr__(self):

return f”LibraryBook(‘{self.title}’, ‘{self.author}’, ‘{self.isbn}’)”

# Using the class

book = LibraryBook(“The Python Magic”, “Feloriya”, “9781234567890”)

print(book) # ‘The Python Magic’ by Feloriya – Available

success, message = book.check_out(“Ali”)

print(message) # Ali checked out ‘The Python Magic’

print(book) # ‘The Python Magic’ by Feloriya – Checked Out

success, message = book.check_out(“Sara”)

print(message) # ‘The Python Magic’ is already checked out

book.return_book()

print(book) # ‘The Python Magic’ by Feloriya – Available

print(book.checkout_history) # [‘Ali’]

print(LibraryBook.validate_isbn(“9781234567890”)) # True

Naming Conventions for Classes and Methods
Following naming conventions makes your code recognizable to other Python programmers.
WhatConventionExample
Class namePascalCaseBankAccount
Instance methodsnake_caseget_balance()
Class methodsnake_casefrom_string()
Static methodsnake_casevalidate_email()
Instance attributesnake_caseself.customer_name
Private (internal) attribute_single_leading_underscoreself._balance
Class attributesnake_case (or UPPER_CASE for constants)MAX_SIZE
Common Mistakes When Writing Classes
  • Forgetting the self parameter in method definitions
  • Forgetting to use self when accessing attributes inside methods
  • Leaving the class empty (use functions until you need a class)
  • Creating classes that are too large (single responsibility)
  • Using class attributes when you meant instance attributes (leads to shared state bugs)
  • Returning self for method chaining (not always appropriate)
Check Your Understanding
  • Write a Student class with attributes name and grades (a list). Add a method add_grade(grade) and average().
  • What is the difference between an instance method and a class method?
  • When would you use a static method?
  • What does the __init__ method do?
  • How do you create an object from a class?
  • Write a BankAccount class with deposit() and withdraw() methods.

⚡ Whisper

Writing your first class is a milestone. You move from using objects to creating them. From being a passenger to being a designer. The syntax is small: class, def __init__, self. But the implications are large. You are now modeling the world. A Car is not just data. It is a thing that can drive and repaint. A Student is not just a name. It is a person who can add grades and calculate averages. You decide what attributes matter. You decide what behaviors are possible. This is power. This is responsibility. Start small. Build simple classes. Then build classes that use other classes. Soon you will see systems where you never imagined. The blueprint is in your hands. The objects are waiting. Create them.

Related posts