
A Python class is a blueprint or template that defines the structure and behavior of objects. Think of a Python class as a cookie cutter that shapes cookies - the class defines what the cookie will look like, while the actual cookies are the objects created from that class.
An object in Python is an instance of a class. When you create an object from a Python class, you’re instantiating that class. Each object has its own set of attributes (data) and methods (functions) defined by the class.
# Simple class definition
class Car:
pass # Empty class body
# Creating objects from the class
my_car = Car() # my_car is an object of class Car
your_car = Car() # your_car is another object of class Car
Python classes are defined using the class keyword followed by the class name. The class name should follow PascalCase convention (first letter of each word capitalized).
class StudentRecord:
"""A class to represent student information"""
pass
Python class attributes are variables that belong to a class. There are two types of attributes in Python classes:
Instance attributes are specific to each object created from the Python class. Each object maintains its own copy of instance attributes.
class Smartphone:
def __init__(self, brand, model, price):
self.brand = brand # Instance attribute
self.model = model # Instance attribute
self.price = price # Instance attribute
Class attributes are shared by all objects of the Python class. They belong to the class itself rather than individual objects.
class BankAccount:
bank_name = "Python Bank" # Class attribute
interest_rate = 0.03 # Class attribute
def __init__(self, account_holder, balance):
self.account_holder = account_holder # Instance attribute
self.balance = balance # Instance attribute
The __init__ method is a special method called a constructor in Python classes. This method is automatically called when you create a new object from the class. The __init__ method initializes the object’s attributes.
class GameCharacter:
def __init__(self, name, health, attack_power):
self.name = name # Initialize instance attribute
self.health = health # Initialize instance attribute
self.attack_power = attack_power # Initialize instance attribute
self.level = 1 # Default attribute value
# Creating objects using __init__
hero = GameCharacter("Warrior", 100, 25)
villain = GameCharacter("Dragon", 200, 40)
Methods are functions defined inside Python classes that operate on objects. There are three types of methods in Python classes:
Instance methods are the most common type of methods in Python classes. They work with instance attributes and can access both instance and class attributes.
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def calculate_area(self): # Instance method
return self.width * self.height
def calculate_perimeter(self): # Instance method
return 2 * (self.width + self.height)
def display_info(self): # Instance method
print(f"Rectangle: {self.width}x{self.height}")
print(f"Area: {self.calculate_area()}")
print(f"Perimeter: {self.calculate_perimeter()}")
Class methods work with class attributes and are decorated with @classmethod. They receive the class as the first argument (conventionally named cls).
class Employee:
company_name = "TechCorp" # Class attribute
total_employees = 0 # Class attribute
def __init__(self, name, salary):
self.name = name
self.salary = salary
Employee.total_employees += 1 # Increment class attribute
@classmethod
def get_company_info(cls): # Class method
return f"Company: {cls.company_name}, Employees: {cls.total_employees}"
@classmethod
def create_intern(cls, name): # Class method as alternative constructor
return cls(name, 30000) # Create object with default salary
Static methods don’t access instance or class attributes. They’re decorated with @staticmethod and behave like regular functions but belong to the class namespace.
class MathOperations:
@staticmethod
def add_numbers(a, b): # Static method
return a + b
@staticmethod
def multiply_numbers(a, b): # Static method
return a * b
@staticmethod
def is_even(number): # Static method
return number % 2 == 0
Creating objects from Python classes is straightforward. You call the class name like a function, passing any required arguments to the __init__ method.
class Book:
def __init__(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
self.is_borrowed = False
def borrow_book(self):
if not self.is_borrowed:
self.is_borrowed = True
return f"You borrowed '{self.title}'"
return f"'{self.title}' is already borrowed"
def return_book(self):
if self.is_borrowed:
self.is_borrowed = False
return f"You returned '{self.title}'"
return f"'{self.title}' was not borrowed"
# Creating and using objects
book1 = Book("Python Programming", "John Doe", 350)
book2 = Book("Data Structures", "Jane Smith", 420)
print(book1.borrow_book()) # Calling method on object
print(book2.title) # Accessing attribute
Python classes allow you to access attributes and methods using dot notation. You can access both instance and class attributes from objects.
class VideoGame:
genre = "Action" # Class attribute
def __init__(self, title, developer, release_year):
self.title = title # Instance attribute
self.developer = developer # Instance attribute
self.release_year = release_year # Instance attribute
def game_info(self):
return f"{self.title} by {self.developer} ({self.release_year})"
# Creating object and accessing attributes
game = VideoGame("Adventure Quest", "GameStudio", 2023)
# Accessing instance attributes
print(game.title) # Adventure Quest
print(game.developer) # GameStudio
# Accessing class attribute
print(game.genre) # Action
print(VideoGame.genre) # Action (accessing via class)
# Calling methods
print(game.game_info()) # Adventure Quest by GameStudio (2023)
Python classes support inheritance, allowing you to create new classes based on existing ones. The new class (child/subclass) inherits attributes and methods from the parent class (superclass).
class Animal: # Parent class
def __init__(self, name, species):
self.name = name
self.species = species
def make_sound(self):
return f"{self.name} makes a sound"
def move(self):
return f"{self.name} moves"
class Dog(Animal): # Child class inheriting from Animal
def __init__(self, name, breed):
super().__init__(name, "Canine") # Call parent __init__
self.breed = breed
def make_sound(self): # Override parent method
return f"{self.name} barks"
def fetch(self): # New method specific to Dog
return f"{self.name} fetches the ball"
class Cat(Animal): # Another child class
def __init__(self, name, color):
super().__init__(name, "Feline")
self.color = color
def make_sound(self): # Override parent method
return f"{self.name} meows"
def climb(self): # New method specific to Cat
return f"{self.name} climbs the tree"
Encapsulation in Python classes involves controlling access to attributes and methods. Python uses naming conventions to indicate privacy levels:
class BankAccount:
def __init__(self, account_number, initial_balance):
self.account_number = account_number # Public attribute
self._balance = initial_balance # Protected attribute (convention)
self.__pin = 1234 # Private attribute (name mangling)
def deposit(self, amount): # Public method
if amount > 0:
self._balance += amount
return f"Deposited ${amount}. New balance: ${self._balance}"
return "Invalid deposit amount"
def _validate_transaction(self, amount): # Protected method
return amount > 0 and amount <= self._balance
def __encrypt_data(self, data): # Private method
return f"encrypted_{data}"
def withdraw(self, amount, pin):
if pin == self.__pin and self._validate_transaction(amount):
self._balance -= amount
return f"Withdrew ${amount}. New balance: ${self._balance}"
return "Invalid withdrawal"
def get_balance(self): # Public method to access private data
return self._balance
Polymorphism allows objects of different Python classes to be treated as objects of a common base class. This enables writing flexible and reusable code.
class Shape: # Base class
def __init__(self, name):
self.name = name
def area(self): # Method to be overridden
raise NotImplementedError("Subclass must implement area method")
def perimeter(self): # Method to be overridden
raise NotImplementedError("Subclass must implement perimeter method")
class Circle(Shape): # Derived class
def __init__(self, radius):
super().__init__("Circle")
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
def perimeter(self):
return 2 * 3.14159 * self.radius
class Square(Shape): # Derived class
def __init__(self, side):
super().__init__("Square")
self.side = side
def area(self):
return self.side ** 2
def perimeter(self):
return 4 * self.side
# Polymorphism in action
def print_shape_info(shape): # Function accepts any Shape object
print(f"{shape.name}:")
print(f"Area: {shape.area()}")
print(f"Perimeter: {shape.perimeter()}")
Python classes can implement special methods (also called magic methods or dunder methods) to customize object behavior:
class Product:
def __init__(self, name, price, quantity):
self.name = name
self.price = price
self.quantity = quantity
def __str__(self): # String representation for users
return f"{self.name} - ${self.price} (Qty: {self.quantity})"
def __repr__(self): # String representation for developers
return f"Product('{self.name}', {self.price}, {self.quantity})"
def __eq__(self, other): # Equality comparison
if isinstance(other, Product):
return self.name == other.name and self.price == other.price
return False
def __lt__(self, other): # Less than comparison
if isinstance(other, Product):
return self.price < other.price
return NotImplemented
def __add__(self, other): # Addition operator
if isinstance(other, Product):
if self.name == other.name:
return Product(self.name, self.price, self.quantity + other.quantity)
return NotImplemented
def __len__(self): # Length operator
return self.quantity
Here’s a comprehensive example that demonstrates Python classes and objects in a real-world scenario:
from datetime import datetime, timedelta
class Book:
"""Represents a book in the library"""
total_books = 0 # Class attribute
def __init__(self, title, author, isbn, copies=1):
self.title = title
self.author = author
self.isbn = isbn
self.copies = copies
self.available_copies = copies
self.borrowed_by = [] # List of members who borrowed this book
Book.total_books += 1
def __str__(self):
return f"'{self.title}' by {self.author}"
def __repr__(self):
return f"Book('{self.title}', '{self.author}', '{self.isbn}', {self.copies})"
def is_available(self):
"""Check if book is available for borrowing"""
return self.available_copies > 0
def borrow(self, member):
"""Borrow a book to a member"""
if self.is_available():
self.available_copies -= 1
self.borrowed_by.append(member)
return True
return False
def return_book(self, member):
"""Return a book from a member"""
if member in self.borrowed_by:
self.available_copies += 1
self.borrowed_by.remove(member)
return True
return False
@classmethod
def get_total_books(cls):
"""Get total number of books in the system"""
return cls.total_books
class Member:
"""Represents a library member"""
def __init__(self, name, member_id, email):
self.name = name
self.member_id = member_id
self.email = email
self.borrowed_books = [] # List of borrowed books
self.join_date = datetime.now()
def __str__(self):
return f"Member: {self.name} (ID: {self.member_id})"
def __repr__(self):
return f"Member('{self.name}', '{self.member_id}', '{self.email}')"
def borrow_book(self, book):
"""Borrow a book"""
if len(self.borrowed_books) < 5: # Maximum 5 books per member
if book.borrow(self):
self.borrowed_books.append(book)
return f"Successfully borrowed {book}"
return f"'{book.title}' is not available"
return "Maximum borrowing limit reached (5 books)"
def return_book(self, book):
"""Return a borrowed book"""
if book in self.borrowed_books:
if book.return_book(self):
self.borrowed_books.remove(book)
return f"Successfully returned {book}"
return f"You haven't borrowed '{book.title}'"
def get_borrowed_books(self):
"""Get list of borrowed books"""
return [str(book) for book in self.borrowed_books]
class Library:
"""Represents the library system"""
def __init__(self, name, address):
self.name = name
self.address = address
self.books = [] # List of all books
self.members = [] # List of all members
self.created_date = datetime.now()
def __str__(self):
return f"{self.name} Library"
def add_book(self, book):
"""Add a book to the library"""
self.books.append(book)
return f"Added {book} to the library"
def add_member(self, member):
"""Add a member to the library"""
self.members.append(member)
return f"Added {member} to the library"
def find_book(self, title):
"""Find a book by title"""
for book in self.books:
if book.title.lower() == title.lower():
return book
return None
def find_member(self, member_id):
"""Find a member by ID"""
for member in self.members:
if member.member_id == member_id:
return member
return None
def get_available_books(self):
"""Get list of available books"""
return [book for book in self.books if book.is_available()]
def get_library_stats(self):
"""Get library statistics"""
total_books = len(self.books)
available_books = len(self.get_available_books())
total_members = len(self.members)
return {
'total_books': total_books,
'available_books': available_books,
'borrowed_books': total_books - available_books,
'total_members': total_members
}
# Example usage and testing
if __name__ == "__main__":
# Create library
city_library = Library("City Central Library", "123 Main Street")
# Create books
book1 = Book("Python Programming", "John Smith", "978-1234567890", 3)
book2 = Book("Data Structures", "Jane Doe", "978-1234567891", 2)
book3 = Book("Web Development", "Bob Johnson", "978-1234567892", 1)
# Add books to library
print(city_library.add_book(book1))
print(city_library.add_book(book2))
print(city_library.add_book(book3))
# Create members
member1 = Member("Alice Brown", "M001", "alice@email.com")
member2 = Member("Charlie Davis", "M002", "charlie@email.com")
# Add members to library
print(city_library.add_member(member1))
print(city_library.add_member(member2))
# Borrow books
print(member1.borrow_book(book1))
print(member1.borrow_book(book2))
print(member2.borrow_book(book1))
# Display member's borrowed books
print(f"Alice's borrowed books: {member1.get_borrowed_books()}")
print(f"Charlie's borrowed books: {member2.get_borrowed_books()}")
# Display library statistics
stats = city_library.get_library_stats()
print(f"\nLibrary Statistics:")
print(f"Total books: {stats['total_books']}")
print(f"Available books: {stats['available_books']}")
print(f"Borrowed books: {stats['borrowed_books']}")
print(f"Total members: {stats['total_members']}")
# Return a book
print(f"\n{member1.return_book(book1)}")
# Check updated statistics
stats = city_library.get_library_stats()
print(f"\nUpdated Statistics:")
print(f"Available books: {stats['available_books']}")
print(f"Borrowed books: {stats['borrowed_books']}")
# Display total books using class method
print(f"\nTotal books in system: {Book.get_total_books()}")
Expected Output:
Added 'Python Programming' by John Smith to the library
Added 'Data Structures' by Jane Doe to the library
Added 'Web Development' by Bob Johnson to the library
Added Member: Alice Brown (ID: M001) to the library
Added Member: Charlie Davis (ID: M002) to the library
Successfully borrowed 'Python Programming' by John Smith
Successfully borrowed 'Data Structures' by Jane Doe
Successfully borrowed 'Python Programming' by John Smith
Alice's borrowed books: ["'Python Programming' by John Smith", "'Data Structures' by Jane Doe"]
Charlie's borrowed books: ["'Python Programming' by John Smith"]
Library Statistics:
Total books: 3
Available books: 1
Borrowed books: 2
Total members: 2
Successfully returned 'Python Programming' by John Smith
Updated Statistics:
Available books: 2
Borrowed books: 1
Total books in system: 3
This comprehensive example demonstrates Python classes and objects working together in a practical library management system, showcasing inheritance, encapsulation, method types, and real-world object interactions. The code includes proper error handling, data validation, and demonstrates how Python classes and objects can model complex real-world systems effectively.