Object-Oriented Programming
## Learning Objectives
- Understand classes and objects
- Master inheritance and polymorphism
- Learn encapsulation and data hiding
- Work with special methods
## Classes and Objects
### Defining a Class
```python
class Dog:
"""A simple dog class."""
def __init__(self, name, age):
self.name = name # Instance attribute
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 an instance
my_dog = Dog("Buddy", 3)
print(my_dog.bark()) # Buddy says Woof!
print(my_dog.get_info()) # Buddy is 3 years old
```
### The __init__ Method
```python
class Dog:
def __init__(self, name, age=1):
self.name = name
self.age = age
```
### Instance Attributes
```python
dog1 = Dog("Rex", 2)
dog2 = Dog("Max")
print(dog1.name) # Rex
print(dog2.age) # 1 (default)
```
## Class Attributes
```python
class Dog:
species = "Canis familiaris" # Class attribute
def __init__(self, name, age):
self.name = name # Instance attribute
self.age = age
print(Dog.species) # Canis familiaris
print(dog1.species) # Canis familiaris
```
## Instance vs Class Methods
### Instance Methods
```python
class Dog:
def __init__(self, name):
self.name = name
def bark(self): # Instance method - takes self
return f"{self.name} barks!"
```
### Class Methods
```python
class Dog:
species = "Canis"
@classmethod
def get_species(cls):
return cls.species
print(Dog.get_species()) # Canis
```
### Static Methods
```python
class Math:
@staticmethod
def add(a, b):
return a + b
print(Math.add(2, 3)) # 5
```
## Inheritance
### Basic Inheritance
```python
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError
class Dog(Animal): # Inherits from Animal
def speak(self):
return f"{self.name} says Woof!"
class Cat(Animal):
def speak(self):
return f"{self.name} says Meow!"
dog = Dog("Buddy")
cat = Cat("Whiskers")
print(dog.speak()) # Buddy says Woof!
print(cat.speak()) # Whiskers says Meow!
```
### The super() Function
```python
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # Call parent's __init__
self.breed = breed
dog = Dog("Rex", "Golden Retriever")
print(dog.name) # Rex
print(dog.breed) # Golden Retriever
```
### Multiple Inheritance
```python
class Flyable:
def fly(self):
return "Flying!"
class Swimmable:
def swim(self):
return "Swimming!"
class Duck(Flyable, Swimmable):
pass
duck = Duck()
print(duck.fly()) # Flying!
print(duck.swim()) # Swimming!
```
## Polymorphism
```python
def make_speak(animal):
print(animal.speak())
animals = [Dog("Rex"), Cat("Whiskers")]
for animal in animals:
make_speak(animal)
# Rex says Woof!
# Whiskers says Meow!
```
## Encapsulation
### Private Attributes
```python
class BankAccount:
def __init__(self, balance):
self._balance = balance # Convention: private
def get_balance(self):
return self._balance
def deposit(self, amount):
if amount > 0:
self._balance += amount
return True
return False
account = BankAccount(100)
print(account.get_balance()) # 100
# account._balance # Still accessible but discouraged
```
### Name Mangling
```python
class MyClass:
def __init__(self):
self.__private = 42 # Name mangled to _MyClass__private
obj = MyClass()
print(obj._MyClass__private) # 42 (don't do this)
```
### Property Decorator
```python
class BankAccount:
def __init__(self, balance):
self._balance = balance
@property
def balance(self):
return self._balance
@balance.setter
def balance(self, value):
if value >= 0:
self._balance = value
else:
raise ValueError("Balance cannot be negative")
account = BankAccount(100)
print(account.balance) # 100
account.balance = 200 # Uses setter
```
## Special Methods (Magic Methods)
### __str__ and __repr__
```python
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"{self.name}, {self.age}"
def __repr__(self):
return f"Person('{self.name}', {self.age})"
p = Person("Alice", 30)
print(str(p)) # Alice, 30
print(repr(p)) # Person('Alice', 30)
```
### __eq__ and __hash__
```python
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __hash__(self):
return hash((self.x, self.y))
p1 = Point(1, 2)
p2 = Point(1, 2)
print(p1 == p2) # True
print(hash(p1) == hash(p2)) # True (can use in sets/dicts)
```
### __len__ and __getitem__
```python
class Inventory:
def __init__(self):
self.items = []
def add(self, item):
self.items.append(item)
def __len__(self):
return len(self.items)
def __getitem__(self, index):
return self.items[index]
inv = Inventory()
inv.add("apple")
inv.add("banana")
print(len(inv)) # 2
print(inv[0]) # apple
```
### __call__
```python
class Multiplier:
def __init__(self, factor):
self.factor = factor
def __call__(self, x):
return x * self.factor
double = Multiplier(2)
print(double(5)) # 10 (called like a function)
```
## Abstract Classes
```python
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
class Rectangle(Shape):
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)
# shape = Shape() # TypeError: Can't instantiate abstract class
rect = Rectangle(5, 3)
print(rect.area()) # 15
```
## Data Classes (Python 3.7+)
```python
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int
email: str = "unknown@example.com" # Default
person = Person("Alice", 30)
print(person) # Person(name='Alice', age=30, email='unknown@example.com')
```
## Summary
- Classes define objects with attributes and methods
- `__init__` initializes new instances
- Instance attributes belong to objects; class attributes to class
- `@classmethod` and `@staticmethod` for alternative method types
- Inheritance: `class Child(Parent):`
- `super().__init__()` calls parent constructor
- Use `_attr` convention for private attributes
- `@property` creates managed attributes
- Special methods: `__str__`, `__eq__`, `__init__`, etc.
- `@dataclass` auto-generates boilerplate
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →