Classes & Objects
Checking access...
Python is an object-oriented language — everything is an object, and classes define how objects behave.
Defining a Class
class Dog: """A simple Dog class."""
# Class attribute — shared by all instances species = "Canis familiaris"
# Constructor — called when creating a new instance 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!"
def description(self): return f"{self.name} is {self.age} years old"Creating and Using Objects
# Create instancesbuddy = Dog("Buddy", 3)max_dog = Dog("Max", 5)
# Access attributesprint(buddy.name) # Buddyprint(buddy.species) # Canis familiaris (class attribute)
# Call methodsprint(buddy.bark()) # Buddy says woof!print(max_dog.description()) # Max is 5 years old
# Modify attributesbuddy.age = 4print(buddy.description()) # Buddy is 4 years oldThe self Parameter
class Example: def method(self, arg): # self — the instance that called the method print(f"Instance: {self}") print(f"Arg: {arg}")
obj = Example()obj.method("hello")# Same as:Example.method(obj, "hello")Instance vs Class Attributes
class Employee: # Class attribute — shared company = "Tech Corp" employee_count = 0
def __init__(self, name, salary): # Instance attributes — unique self.name = name self.salary = salary Employee.employee_count += 1
def info(self): return f"{self.name} works at {Employee.company}"
e1 = Employee("Alice", 70000)e2 = Employee("Bob", 80000)
print(e1.company) # Tech Corpprint(e2.company) # Tech Corpprint(Employee.company) # Tech Corp
# Modify class attribute via instance — creates instance attributee1.company = "Startup"print(e1.company) # Startup — instance attribute shadows classprint(e2.company) # Tech Corp — class attribute unchangedprint(Employee.company) # Tech CorpTip
Access class attributes through the class name (Employee.company), not through self, to avoid accidentally creating instance attributes.
Instance Methods, Class Methods, Static Methods
class Circle: pi = 3.14159
def __init__(self, radius): self.radius = radius
# Instance method — works with instance data def area(self): return Circle.pi * self.radius ** 2
# Class method — receives class, not instance @classmethod def from_diameter(cls, diameter): return cls(diameter / 2)
# Static method — no self or cls, just utility @staticmethod def is_valid_radius(radius): return radius > 0
# Instance methodc = Circle(5)print(c.area()) # 78.53975
# Class method — alternative constructorc2 = Circle.from_diameter(10)print(c2.radius) # 5.0
# Static method — utility functionprint(Circle.is_valid_radius(-1)) # FalseProperty Decorators
Control attribute access with getters/setters:
class Temperature: def __init__(self, celsius): self._celsius = celsius
@property def celsius(self): """Get temperature in Celsius.""" return self._celsius
@celsius.setter def celsius(self, value): if value < -273.15: raise ValueError("Below absolute zero!") self._celsius = value
@property def fahrenheit(self): """Get temperature in Fahrenheit (computed).""" return (self._celsius * 9/5) + 32
temp = Temperature(25)print(temp.celsius) # 25 — uses getterprint(temp.fahrenheit) # 77.0 — computed
temp.celsius = 30 # Uses setter with validation# temp.celsius = -300 # ValueError!Dunder Methods
Special methods that customize object behavior:
class Vector: def __init__(self, x, y): self.x = x self.y = y
# String representation def __str__(self): return f"Vector({self.x}, {self.y})"
def __repr__(self): return f"Vector({self.x!r}, {self.y!r})"
# Arithmetic def __add__(self, other): return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other): return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar): return Vector(self.x * scalar, self.y * scalar)
# Comparison def __eq__(self, other): return self.x == other.x and self.y == other.y
# Length def __abs__(self): return (self.x ** 2 + self.y ** 2) ** 0.5
v1 = Vector(3, 4)v2 = Vector(1, 2)
print(v1) # Vector(3, 4) — __str__print(v1 + v2) # Vector(4, 6) — __add__print(v1 * 2) # Vector(6, 8) — __mul__print(abs(v1)) # 5.0 — __abs__print(v1 == Vector(3, 4)) # True — __eq__Inheritance
class Animal: def __init__(self, name): self.name = name
def speak(self): raise NotImplementedError("Subclass must implement")
class Cat(Animal): def speak(self): return f"{self.name} says meow!"
class Dog(Animal): def speak(self): return f"{self.name} says woof!"
animals = [Cat("Whiskers"), Dog("Buddy")]for animal in animals: print(animal.speak())# Whiskers says meow!# Buddy says woof!Key Takeaways
- Classes define blueprints;
__init__initializes instances selfrefers to the instance — always the first parameter of instance methods- Class attributes are shared; instance attributes are unique
@classmethodreceives the class;@staticmethodreceives nothing special@propertycreates computed attributes with getter/setter- Dunder methods (
__str__,__add__,__eq__, etc.) customize object behavior - Inheritance lets subclasses extend parent behavior;
super()calls parent methods