Python Classes and Objects
Classes and objects are the foundation of object-oriented programming in Python. Classes define blueprints for creating objects, which are instances of those classes. This tutorial covers the basics of creating and using classes.
What are Classes and Objects?
Classes are templates for creating objects. Objects are instances of classes with their own data and behavior.
Basic Class Definition
# Define a simple class
class Dog:
"""A simple Dog class."""
# Class attribute (shared by all instances)
species = "Canis familiaris"
# Constructor method
def __init__(self, name, age):
# Instance attributes (unique to each instance)
self.name = name
self.age = age
# Instance method
def bark(self):
return f"{self.name} says woof!"
# Create objects (instances)
dog1 = Dog("Buddy", 3)
dog2 = Dog("Max", 5)
print(dog1.bark()) # Buddy says woof!
print(dog2.bark()) # Max says woof!Classes use the class keyword. The __init__ method initializes new objects. See class definition in the tutorial.
Instance Attributes and Methods
Instance attributes store data unique to each object. Instance methods define object behavior.
The self Parameter
class Person:
def __init__(self, name, age):
self.name = name # Instance attribute
self.age = age # Instance attribute
def introduce(self):
return f"Hi, I'm {self.name} and I'm {self.age} years old."
def have_birthday(self):
self.age += 1
return f"Happy birthday! Now I'm {self.age}."
# Create and use person
person = Person("Alice", 30)
print(person.introduce())
person.have_birthday()
print(person.introduce())self refers to the current instance. It’s required for instance methods. Learn about instance methods.
Class Attributes
Class attributes are shared by all instances of the class.
class Car:
# Class attributes
wheels = 4
engine_type = "gasoline"
def __init__(self, make, model, year):
self.make = make # Instance attribute
self.model = model # Instance attribute
self.year = year # Instance attribute
# All cars share these attributes
car1 = Car("Toyota", "Camry", 2020)
car2 = Car("Honda", "Civic", 2021)
print(f"All cars have {Car.wheels} wheels")
print(f"Car1: {car1.make} {car1.model}")
print(f"Car2: {car2.make} {car2.model}")Class attributes are accessed via the class name. See class and instance attributes.
Methods
Methods define what objects can do. There are instance, class, and static methods.
Instance Methods
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
if amount > 0:
self.balance += amount
return f"Deposited ${amount}. New balance: ${self.balance}"
return "Invalid deposit amount"
def withdraw(self, amount):
if 0 < amount <= self.balance:
self.balance -= amount
return f"Withdrew ${amount}. New balance: ${self.balance}"
return "Invalid withdrawal amount"
def get_balance(self):
return f"Account balance: ${self.balance}"
# Use the account
account = BankAccount("Alice", 1000)
print(account.deposit(500))
print(account.withdraw(200))
print(account.get_balance())Instance methods work with individual object data.
Class Methods
Class methods work with class-level data and are called on the class.
class Student:
# Class attribute to track all students
all_students = []
def __init__(self, name, grade):
self.name = name
self.grade = grade
# Add to class list
Student.all_students.append(self)
@classmethod
def get_total_students(cls):
return len(cls.all_students)
@classmethod
def create_from_string(cls, student_string):
name, grade = student_string.split(",")
return cls(name.strip(), grade.strip())
# Create students
student1 = Student("Alice", "A")
student2 = Student("Bob", "B")
student3 = Student.create_from_string("Charlie, A")
print(f"Total students: {Student.get_total_students()}")Class methods use @classmethod decorator and cls parameter. See class methods.
Static Methods
Static methods don’t access instance or class data.
class MathUtils:
@staticmethod
def add(a, b):
return a + b
@staticmethod
def is_even(number):
return number % 2 == 0
@staticmethod
def factorial(n):
if n <= 1:
return 1
return n * MathUtils.factorial(n - 1)
# Use static methods
result = MathUtils.add(5, 3)
even = MathUtils.is_even(4)
fact = MathUtils.factorial(5)
print(f"5 + 3 = {result}")
print(f"4 is even: {even}")
print(f"5! = {fact}")Static methods use @staticmethod decorator. They’re utility functions in classes. See static methods.
Properties
Properties allow controlled access to attributes.
class Temperature:
def __init__(self, celsius=0):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("Temperature can't be below absolute zero")
self._celsius = value
@property
def fahrenheit(self):
return (self._celsius * 9/5) + 32
@fahrenheit.setter
def fahrenheit(self, value):
self._celsius = (value - 32) * 5/9
# Use properties
temp = Temperature(20)
print(f"Celsius: {temp.celsius}°C")
print(f"Fahrenheit: {temp.fahrenheit}°F")
temp.fahrenheit = 68
print(f"New Celsius: {temp.celsius}°C")Properties use @property decorator. They provide getter/setter functionality. Learn about properties.
Inheritance
Classes can inherit from other classes, gaining their attributes and methods.
Basic Inheritance
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "Some sound"
class Dog(Animal):
def speak(self):
return "Woof!"
class Cat(Animal):
def speak(self):
return "Meow!"
# Create instances
dog = Dog("Buddy")
cat = Cat("Whiskers")
print(f"{dog.name} says: {dog.speak()}")
print(f"{cat.name} says: {cat.speak()}")Child classes inherit from parent classes. The speak method is overridden. See inheritance.
super() Function
Use super() to call parent class methods.
class Vehicle:
def __init__(self, make, model):
self.make = make
self.model = model
def get_info(self):
return f"{self.make} {self.model}"
class ElectricVehicle(Vehicle):
def __init__(self, make, model, battery_range):
super().__init__(make, model) # Call parent constructor
self.battery_range = battery_range
def get_info(self):
base_info = super().get_info() # Call parent method
return f"{base_info} - {self.battery_range} miles range"
# Use inheritance
tesla = ElectricVehicle("Tesla", "Model 3", 358)
print(tesla.get_info())super() accesses parent class methods. It’s essential for inheritance. See super().
Special Methods
Special methods customize object behavior.
String Representation
class Book:
def __init__(self, title, author, year):
self.title = title
self.author = author
self.year = year
def __str__(self):
return f"{self.title} by {self.author} ({self.year})"
def __repr__(self):
return f"Book('{self.title}', '{self.author}', {self.year})"
book = Book("1984", "George Orwell", 1949)
print(str(book)) # User-friendly string
print(repr(book)) # Developer-friendly string__str__ for user display, __repr__ for debugging. See special methods.
Comparison Methods
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
return isinstance(other, Person) and self.age == other.age
def __lt__(self, other):
return isinstance(other, Person) and self.age < other.age
def __le__(self, other):
return self < other or self == other
person1 = Person("Alice", 30)
person2 = Person("Bob", 25)
person3 = Person("Charlie", 30)
print(person1 == person3) # True (same age)
print(person1 > person2) # True (older)Comparison operators can be customized. Learn about rich comparisons.
Class Design Principles
Encapsulation
Hide internal details and provide a clean interface.
class EmailService:
def __init__(self, smtp_server):
self._smtp_server = smtp_server # Private attribute
self._connection = None
def _connect(self): # Private method
# Internal connection logic
pass
def send_email(self, to, subject, body): # Public interface
self._connect()
# Send email logic
return f"Email sent to {to}"
service = EmailService("smtp.example.com")
service.send_email("user@example.com", "Hello", "Message body")
# service._connect() # Should not call private methods directlyUse underscore prefix for private members. See encapsulation.
Composition over Inheritance
Favor composition when possible.
class Engine:
def start(self):
return "Engine started"
class Car:
def __init__(self, make, model):
self.make = make
self.model = model
self.engine = Engine() # Composition
def start(self):
return f"{self.make} {self.model}: {self.engine.start()}"
car = Car("Toyota", "Camry")
print(car.start())Composition is often more flexible than inheritance. See composition examples.
Common Patterns
Factory Pattern
class Shape:
@staticmethod
def create_circle(radius):
return Circle(radius)
@staticmethod
def create_square(side):
return Square(side)
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
class Square:
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
# Use factory
circle = Shape.create_circle(5)
square = Shape.create_square(4)
print(f"Circle area: {circle.area()}")
print(f"Square area: {square.area()}")Factory methods create objects without specifying exact classes.
Best Practices
- Use descriptive class and method names
- Keep classes focused on single responsibilities
- Use properties for attribute access control
- Prefer composition over complex inheritance
- Document classes and methods with docstrings
External Resources:
Related Tutorials: