Site

Classes — Python Classes and Objects

Tutorial 6.0  •  Python / Learn

6.0 What This Teaches

This tutorial covers how Python defines and uses classes:

6.1 Class Syntax

A class groups related data and behavior. The class keyword followed by an indented body defines the class. Python classes don't need forward declarations or header files:
class Point:
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

    def distance(self) -> float:
        return (self.x ** 2 + self.y ** 2) ** 0.5

p = Point(3, 4)
print(p.distance())  # 5.0
print(p.x, p.y)      # 3 4
Every instance method receives the instance as its first argument, named self by convention. You write it in the definition but never pass it explicitly at the call site.

6.2 Instance Attributes and __init__

Instance attributes are created by assigning to self.name inside any method, though __init__ is the right place to create them all. Each instance gets its own independent copy:
class Person:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

    def greet(self) -> str:
        return f"Hi, I'm {self.name}, age {self.age}."

alice = Person("Alice", 30)
bob = Person("Bob", 25)

print(alice.greet())   # Hi, I'm Alice, age 30.
alice.age = 31         # attributes are public by default
print(alice.age)       # 31
Python doesn't have private attributes - convention uses a leading underscore (_name) to signal "internal use only."

6.3 Methods

Regular methods take self. Class methods receive the class as cls and are used as alternative constructors. Static methods receive neither and are just functions grouped in the class namespace:
class Temperature:
    def __init__(self, celsius: float):
        self.celsius = celsius

    def to_fahrenheit(self) -> float:
        return self.celsius * 9 / 5 + 32

    @classmethod
    def from_fahrenheit(cls, f: float) -> "Temperature":
        return cls((f - 32) * 5 / 9)

    @staticmethod
    def absolute_zero() -> float:
        return -273.15

t = Temperature(100)
print(t.to_fahrenheit())                        # 212.0
print(Temperature.from_fahrenheit(32).celsius)  # 0.0
print(Temperature.absolute_zero())              # -273.15

6.4 Class Attributes

Class attributes are defined in the class body outside any method. All instances share the same class attribute unless an instance shadows it with its own:
class Counter:
    count = 0   # class attribute

    def __init__(self, name: str):
        self.name = name
        Counter.count += 1   # modify via class name, not self
        self.id = Counter.count

c1 = Counter("first")
c2 = Counter("second")
print(Counter.count)   # 2
print(c1.id, c2.id)    # 1 2
Use class attributes for shared constants or counters. Never use a mutable class attribute (list, dict) as a per-instance default - it will be shared across all instances (see Common Mistakes).

6.5 Inheritance

List the parent class in parentheses. Call super().__init__() to initialize the base class. Overriding a method just means defining one with the same name - no keyword required:
class Animal:
    def __init__(self, name: str):
        self.name = name

    def sound(self) -> str:
        return "..."

    def describe(self) -> str:
        return f"{self.name} says {self.sound()}"

class Dog(Animal):
    def sound(self) -> str:
        return "Woof"

class Cat(Animal):
    def sound(self) -> str:
        return "Meow"

animals = [Dog("Rex"), Cat("Whiskers")]
for a in animals:
    print(a.describe())
Expected output:
Rex says Woof
Whiskers says Meow

6.6 Special Methods

Dunder (double-underscore) methods let your class integrate with Python's built-in operations. __str__ is called by str() and print(); __repr__ is called by repr() and the REPL:
class Vector:
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

    def __repr__(self) -> str:
        return f"Vector({self.x}, {self.y})"

    def __str__(self) -> str:
        return f"({self.x}, {self.y})"

    def __add__(self, other: "Vector") -> "Vector":
        return Vector(self.x + other.x, self.y + other.y)

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Vector):
            return NotImplemented
        return self.x == other.x and self.y == other.y

v1, v2 = Vector(1, 2), Vector(3, 4)
print(v1 + v2)          # (4, 6) - calls __str__
print(repr(v1))         # Vector(1, 2)
print(v1 == Vector(1, 2))  # True

6.7 Example - All Together

# Classes - shapes hierarchy with area and describe methods.

import math

class Shape:
    def area(self) -> float:
        raise NotImplementedError

    def describe(self) -> str:
        return f"{type(self).__name__} area={self.area():.2f}"

class Circle(Shape):
    def __init__(self, radius: float):
        self.radius = radius

    def area(self) -> float:
        return math.pi * self.radius ** 2

class Rectangle(Shape):
    def __init__(self, width: float, height: float):
        self.width = width
        self.height = height

    def area(self) -> float:
        return self.width * self.height

shapes: list[Shape] = [Circle(3), Rectangle(4, 5), Circle(1)]
for s in shapes:
    print(s.describe())
Expected output:
Circle area=28.27
Rectangle area=20.00
Circle area=3.14

6.8 Exercise

Exercise
  • Define a Student class with name and grade (int) attributes. Add a letter_grade property that returns A/B/C/D/F.
  • Add a class attribute roster (list) and a class method register(cls, student) that appends to it.
  • Implement __str__ to return a readable summary. Print all students in the roster.

6.9 Common Mistakes

Forgetting self in method definition

class Foo:
    def greet():   # missing self
        print("hi")

Foo().greet()   # TypeError: greet() takes 0 positional arguments but 1 was given
Every instance method must have self as its first parameter.

Mutable class attribute shared across all instances

class Team:
    members = []   # shared by ALL Team instances

t1 = Team()
t2 = Team()
t1.members.append("Alice")
print(t2.members)   # ['Alice'] - unexpected!
Define mutable attributes in __init__: self.members = []

type() instead of isinstance() for type checks

if type(x) == Animal:      # True only for Animal exactly
    ...
if isinstance(x, Animal):  # True for Animal and all subclasses - usually correct
    ...

6.10 Key Terms

TermMeaning
classBlueprint for creating objects that group data and behavior
__init__Initializer called when an instance is created; sets up instance attributes
selfConventional name for the instance passed as first argument to methods
instance attributeData stored on each individual object via self.name
class attributeData shared by all instances; defined at class body level
@classmethodMethod receiving the class (cls) instead of an instance; used for factory constructors
@staticmethodMethod receiving neither self nor cls; a plain function in class scope
dunder methodSpecial method named with double underscores; hooks into Python built-ins
inheritanceCreating a class that extends another: class Dog(Animal)
super()Proxy to the parent class; used to call parent __init__ and overridden methods