What are Classes and Objects?

A class is a blueprint for creating objects. An object is an instance of a class. Object-oriented programming (OOP) helps organize code by grouping related data and functions together.

Defining a Class

Create a class using the class keyword:

class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def bark(self):
        return f"{self.name} says Woof!"
    
    def get_info(self):
        return f"{self.name} is {self.age} years old"

Creating Objects

Create instances (objects) of a class:

# Create objects
dog1 = Dog("Buddy", 3)
dog2 = Dog("Max", 5)

# Use object methods
print(dog1.bark())        # "Buddy says Woof!"
print(dog2.get_info())    # "Max is 5 years old"

The __init__ Method

The __init__ method is called when an object is created. It initializes the object's attributes:

class Person:
    def __init__(self, name, age):
        self.name = name      # Instance attribute
        self.age = age        # Instance attribute
        print(f"{name} created!")

person = Person("Alice", 25)  # "Alice created!"

Instance Attributes

Attributes that belong to each object:

class Car:
    def __init__(self, brand, model):
        self.brand = brand    # Each car has its own brand
        self.model = model    # Each car has its own model

car1 = Car("Toyota", "Camry")
car2 = Car("Honda", "Civic")

print(car1.brand)            # "Toyota"
print(car2.brand)            # "Honda"

Instance Methods

Methods that operate on the object:

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def area(self):
        return self.width * self.height
    
    def perimeter(self):
        return 2 * (self.width + self.height)

rect = Rectangle(5, 3)
print(rect.area())           # 15
print(rect.perimeter())      # 16

Class Attributes

Attributes shared by all instances:

class Dog:
    species = "Canis familiaris"  # Class attribute (shared)
    
    def __init__(self, name):
        self.name = name           # Instance attribute (unique)

dog1 = Dog("Buddy")
dog2 = Dog("Max")

print(dog1.species)               # "Canis familiaris"
print(dog2.species)               # "Canis familiaris"
print(Dog.species)                # "Canis familiaris"

The self Parameter

self refers to the current instance. It must be the first parameter of instance methods:

class Person:
    def __init__(self, name):
        self.name = name
    
    def introduce(self):
        # self refers to the object calling this method
        return f"Hi, I'm {self.name}"

person = Person("Alice")
print(person.introduce())         # "Hi, I'm Alice"

String Representation

Use __str__ to define how objects are printed:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def __str__(self):
        return f"Person(name={self.name}, age={self.age})"

person = Person("Alice", 25)
print(person)                     # "Person(name=Alice, age=25)"

Private Attributes (Convention)

Use underscore prefix to indicate "private" attributes (convention, not enforced):

class BankAccount:
    def __init__(self, balance):
        self._balance = balance    # Protected (single underscore)
        self.__secret = "hidden"   # Name mangling (double underscore)
    
    def get_balance(self):
        return self._balance

account = BankAccount(1000)
print(account._balance)           # Can access, but shouldn't
# print(account.__secret)         # Error (name mangled)

Properties

Use @property to create getter methods that look like attributes:

class Circle:
    def __init__(self, radius):
        self._radius = radius
    
    @property
    def radius(self):
        return self._radius
    
    @property
    def area(self):
        return 3.14159 * self._radius ** 2

circle = Circle(5)
print(circle.radius)              # 5 (no parentheses!)
print(circle.area)                # 78.53975

Common Patterns

Data Class

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __str__(self):
        return f"Point({self.x}, {self.y})"

point = Point(3, 4)
print(point)                      # "Point(3, 4)"

Class with Methods

class Calculator:
    def __init__(self):
        self.result = 0
    
    def add(self, value):
        self.result += value
        return self
    
    def multiply(self, value):
        self.result *= value
        return self

calc = Calculator()
calc.add(5).multiply(3)
print(calc.result)                # 15

Best Practices

  • Use descriptive names: Class names should be nouns (Dog, Person, Car)
  • Keep classes focused: Each class should have a single responsibility
  • Use __str__: Make objects printable for debugging
  • Document with docstrings: Explain what the class does
  • Initialize all attributes: Set them in __init__

Next Steps

Learn about inheritance to create new classes based on existing ones, or explore modules to organize your classes.