Chapter 10: Object-Oriented Programming
1. Basic Classes - "blueprints for objects"
| Term | What it means | In the code |
|---|---|---|
| Class | A recipe that describes what data an object stores and what it can do. | class Person: ... |
| Object / Instance | A single thing built from that recipe. | person = Person("John", 30) |
Constructor (__init__) | Runs automatically when you create the object; sets up its data. | Stores name and age. |
| Instance method | A function that belongs to every object of the class. | greet() |
Why it matters:
Classes bundle data (name, age) together with behavior (greet) so you can treat "a person" as one coherent unit.
2. Containment (Has-A) - objects inside objects
- A Student has-a list of Courses.
- We model that by storing
self.courses: List[Course] = []insideStudent.
student.add_course(math) # puts a Course object into the list
student.get_courses() # returns ['Mathematics']Takeaway: When something "owns" or "is made of" other objects, store them as attributes-often a list. This keeps related pieces neatly grouped.
3. Inheritance (Is-A) - building families of classes
- Parent / base class: shared code (
Animalwithspeak). - Child / derived class: gets everything the parent has plus its own tweaks (
Dog,Catoverridespeak).
dog = Dog("Buddy") # behaves like an Animal, but its speak() is "Woof!"Why bother? Write common code once, specialize only where needed. This avoids repetition and lets you treat many related objects the same way (polymorphism).
4. Abstract Classes - "contracts" for future classes
class Shape(ABC):
@abstractmethod
def area(self): ...- Mark methods with
@abstractmethodto promise every real Shape must implement them. - You can't create
Shape()directly; you must create a concrete subclass likeRectangle.
Benefit:
Enforces a consistent interface-handy in big projects where many people write different shapes but everyone can trust shape.area() exists.
5. Relational & Arithmetic Operators - making objects act like built-ins
Using the Fraction class:
| Method | Purpose |
|---|---|
__eq__ | == compares two fractions properly. |
__lt__ | < compares them. |
@total_ordering | Fills in >, <=, >= automatically if you supply __eq__ and one ordering method. |
__add__ | Lets you use + to add fractions. |
__str__ | Defines how print(fraction) looks ("1/2"). |
Idea: Overloading dunder (double-underscore) methods lets your objects behave like numbers, strings, etc.-making code feel natural.
6. Iterators and Iterables - custom "for-loopable" objects
To make an object work in a for loop:
-
Iterable (
NumberSequence): must define__iter__, returning an iterator. -
Iterator (
NumberIterator): must define__iter__returningself, and__next__returning the next value or raisingStopIterationat the end.
Result:
for n in NumberSequence(1, 3):
print(n) # 1 2 3Why it's useful: You control how values are produced-great for infinite streams, filtered views, reading files line-by-line, etc.
7. Access Modifiers & Encapsulation - protecting your data
| Prefix | Typical meaning (by convention) | Example |
|---|---|---|
| none | Public - anyone can use it. | account_number |
_single | Protected - "internal use," but not enforced. | _balance |
__double | Private - name mangled (_BankAccount__transactions) to discourage direct access. | __transactions |
Encapsulation hides internal details so the rest of your program can't accidentally break invariants. Interact through methods instead (deposit, get_balance).
8. Quick Recap
- Classes bundle data + behavior.
- Containment ⇒ "has-a" relationships inside objects.
- Inheritance ⇒ "is-a" hierarchy + code reuse.
- Abstract classes set rules future classes must follow.
- Operator overloading makes custom objects feel like built-ins.
- Iterators let your objects power
forloops. - Encapsulation shields internal state; use public methods instead of direct access.