6.0 What This Teaches
classsyntax and the__init__initializer- Instance attributes and the
selfparameter - Instance methods, class methods (
@classmethod), and static methods - Class attributes vs instance attributes
- Inheritance and
super() - Special methods:
__str__,__repr__,__eq__
6.1 Class Syntax
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
self by convention. You write it in the definition but never pass
it explicitly at the call site.
6.2 Instance Attributes and __init__
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
_name) to signal "internal use only."
6.3 Methods
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 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
6.5 Inheritance
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())
Rex says Woof
Whiskers says Meow
6.6 Special Methods
__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())
Circle area=28.27
Rectangle area=20.00
Circle area=3.14
6.8 Exercise
Exercise
- Define a
Studentclass withnameandgrade(int) attributes. Add aletter_gradeproperty that returns A/B/C/D/F. - Add a class attribute
roster(list) and a class methodregister(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
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!
__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
| Term | Meaning |
|---|---|
| class | Blueprint for creating objects that group data and behavior |
| __init__ | Initializer called when an instance is created; sets up instance attributes |
| self | Conventional name for the instance passed as first argument to methods |
| instance attribute | Data stored on each individual object via self.name |
| class attribute | Data shared by all instances; defined at class body level |
| @classmethod | Method receiving the class (cls) instead of an instance; used for factory constructors |
| @staticmethod | Method receiving neither self nor cls; a plain function in class scope |
| dunder method | Special method named with double underscores; hooks into Python built-ins |
| inheritance | Creating a class that extends another: class Dog(Animal) |
| super() | Proxy to the parent class; used to call parent __init__ and overridden methods |