Classes & OOP in Python
Theme
Palette
Download
Caption (tap to copy)
📖 Deep dive (full written explanation)
This cover sets up the most practical post in the day: a code-heavy build of the class features that recur in real work. The framing is deliberate — reading about classes builds recognition, but writing one with a constructor, inheritance, a property, and dunder methods builds the muscle memory that makes OOP automatic.
The goal across these slides is breadth of pattern rather than depth of theory. By the end the reader should have seen and ideally typed the handful of shapes that cover the overwhelming majority of day-to-day class definition, from a basic stateful object to the @dataclass shortcut.
The first pattern is the foundation: a class that holds state and exposes behavior to change it. Account stores an owner and a balance, and deposit() modifies the balance and returns the new value. This is the bread-and-butter shape — data set up in __init__, methods that read and update that data.
Note the leading underscore on _balance, a convention meaning 'internal, treat as private.' Python doesn't enforce it, but it signals that callers should use deposit() rather than poke the balance directly — the encapsulation idea from the why post, made concrete in a naming convention. Running this and adding a withdraw() method that guards against overdrafts is a natural next exercise.
This slide covers inheritance with super(), the most important reuse pattern. Dog inherits from Animal, calls super().__init__(name) to run the parent's setup before adding its own breed attribute, and overrides speak() with its own implementation. The final line shows the overridden method winning.
Two mechanics from the how post are visible here. super().__init__ delegates up the chain so the parent's initialization isn't lost — without it, name would never be set. And the overridden speak() demonstrates the lookup order: Dog's own version is found before Animal's, so it wins. This snippet is the canonical template for extending a base class while reusing its setup.
The @property decorator is one of Python's most elegant features, and this slide shows it doing its core job: turning a method into something accessed like an attribute. Circle.area is defined as a method but called as c.area with no parentheses, computing the value on demand from the radius.
The payoff is a clean interface that hides whether a value is stored or computed. Callers write c.area whether area is a plain attribute or a calculation, so you can start with a stored value and later make it computed without breaking any caller. This is abstraction in action — the simple interface from the concept post's four pillars, delivered through a single decorator.
This slide introduces the dunder methods that make custom objects behave like built-in types. __repr__ gives Point a readable form so print() shows Point(1, 2) instead of an opaque memory address, and __eq__ defines what == means so two points with the same coordinates compare equal.
These two are the highest-value dunders to implement first. Without __repr__, debugging is painful because objects print as unhelpful identifiers. Without __eq__, equality falls back to identity — two distinct objects are never equal even with identical data, which the mistakes post demonstrates as a real bug. Together they make a class debuggable and comparable, the minimum for a class you'll actually work with.
The decision diagram gives a quick mental flowchart for choosing the right tool. If the class is mostly just data fields, reach for @dataclass and let it generate the boilerplate. If you need a value computed from other attributes, use @property. Otherwise, a regular method or plain __init__ setup is the answer.
This picker operationalizes the features the post demonstrates into a concrete choosing procedure. In practice the decision is fast once internalized, but laying it out explicitly helps cement when each tool earns its place — and steers readers toward @dataclass before they hand-write their fourth boilerplate __init__, which is the post's closing lesson.
This slide shows @classmethod as an alternate constructor — a second way to build an instance beyond the default __init__. Date.from_string parses a string and returns a fully built Date by calling cls(y, m, d). The cls parameter is to the class what self is to an instance: a reference to the class itself.
Alternate constructors are a clean solution to a common need: building the same object from different input formats. Rather than overloading __init__ with branching logic for strings versus numbers, you give each input format its own clearly named classmethod. Using cls instead of hard-coding the class name also means subclasses inherit the constructor correctly — calling Date.from_string from a subclass builds the subclass.
The tips slide is a compact reference card of the patterns to commit to memory: __init__ for state setup, super().__init__() to reuse a parent, @property for a method that reads like data, __repr__ for useful printing, and @classmethod for alternate constructors. Each is given in skeletal form so a real task can be matched to the right shape quickly.
These cover the vast majority of practical class definition. Internalizing them as templates — recognizing 'this needs an alternate constructor' or 'this value should be a property' — is what makes writing classes fast. The card is designed to be the thing you glance back at until the shapes become automatic.
This slide names the unifying idea behind the dunder methods scattered through the post: double-underscore methods are the hooks that let your objects plug into Python's built-in syntax and operations. __repr__ powers print(), __eq__ powers ==, __len__ powers len(), __add__ powers the + operator, and so on.
Elevating this from 'some special method names' to a coherent principle matters because it explains why custom classes can feel as natural to use as built-in types. Implement the right dunder and your object responds to native syntax — you can add two of your objects, measure their length, or compare them, all because you defined the corresponding hook. This is the mechanism behind Python's famously seamless object model.
This code slide shows @dataclass eliminating the boilerplate the post just taught you to write by hand. By annotating the fields x and y with their types and decorating the class, Python generates __init__, __repr__, and __eq__ automatically. The Point class becomes three lines and still prints nicely and compares by value.
The contrast with the earlier hand-written Point is the whole point. Everything you typed manually for __repr__ and __eq__ is generated correctly from the field annotations, with no chance of forgetting a field. For the large category of classes that are primarily structured data, @dataclass is the right default — concise, correct, and self-documenting through its type annotations.
The closing mistake names the boundary: hand-writing __init__, __repr__, and __eq__ for a data-holding class is wasted effort and a bug surface. The classic failure is forgetting a field in a hand-written __eq__, so two objects that differ in that field compare as equal — a silent correctness bug that's easy to introduce and hard to spot.
@dataclass generates all of it correctly from the field annotations, guaranteeing every field participates in equality and representation. The practical rule is to reach for @dataclass before writing a fourth boilerplate __init__. Recognizing 'this class is mostly data' as the signal to use a dataclass is a mark of fluency — knowing not just how to write the methods, but when to let the language write them for you.
This CTA closes the hands-on post and points to the final angle: the mistakes that bite everyone. Having built fluency with constructors, inheritance, properties, and dunders, the reader is ready to learn the failure modes that turn well-intentioned classes into bugs and tangled hierarchies.
The teaser promises a field guide to what goes wrong, signaling that the mistakes post consolidates the warnings scattered through the earlier angles — shared mutable state, broken equality, inheritance overuse — into one defensive checklist.