Object Oriented Programming Exercises
Objectives
This chapter introduces no new concepts. It’s a chance to check your understanding of object-oriented programming.
Part I: Recall Questions
- What is a class? What is an instance?
- What is encapsulation? Give an example.
- What is abstraction? Give an example.
- What is inheritance, and how does it reduce code duplication?
- What is multiple inheritance?
- What is polymorphism? Does it require inheritance?
- What is method resolution order (MRO), and how would you check it for a class?
Part II: Properties, Dataclasses, and Exceptions
- Write a
Temperatureclass storing a value in Celsius, with a@propertycalledfahrenheitthat computes and returns the Fahrenheit equivalent, and a matching setter, so assigning to.fahrenheitupdates the underlying Celsius value instead. - Rewrite a simple class you’ve already written in this module (something with just a few stored attributes and no real behavior) as a
@dataclass, and confirm__repr__and__eq__work without you writing them. - Write a custom exception
NegativeDepositError, and use it inside a smallWalletclass whosedeposit(amount)method raises it ifamountis negative.
Part III: Abstract Base Classes
- Define an abstract base class
Shapewith an@abstractmethodcalledarea(). Write two subclasses,CircleandRectangle, each implementingarea()correctly. Confirm that trying to instantiateShapedirectly raises aTypeError.
Part IV: Build a Deck of Cards
Model a standard 52-card deck using two classes.
Card
- Has a
suit(Hearts, Diamonds, Clubs, or Spades) and avalue(A, 2–10, J, Q, K) - Implement
__str__(or__repr__) so printing a card shows something readable, like"A of Spades"
Deck
- On creation, builds all 52 unique
Cardcombinations shuffle(): resets the deck to all 52 cards, then randomizes their order (try therandommodule from the previous module)deal(): removes and returns one card from the deck, so the same card can never be dealt twice in a row without a reshuffle
Starter Structure
import random
SUITS = ["Hearts", "Diamonds", "Clubs", "Spades"]
VALUES = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]
class Card:
def __init__(self, suit, value):
self.suit = suit
self.value = value
def __str__(self):
return f"{self.value} of {self.suit}"
class Deck:
def __init__(self):
self.cards = self._build_full_deck()
def _build_full_deck(self):
return [Card(suit, value) for suit in SUITS for value in VALUES]
def shuffle(self):
self.cards = self._build_full_deck()
random.shuffle(self.cards)
def deal(self):
return self.cards.pop() if self.cards else None
Try extending it further: track how many cards remain, prevent dealing from an empty deck, or add a Hand class that collects cards dealt to a player.
Try It
- Build the
CardandDeckclasses above (or your own version), create a deck, shuffle it, and deal out five cards. - Print
len(deck.cards)before and after dealing to confirm it shrinks by one each time. - Bonus: add a
Handclass that aDeckcan deal cards into. - Build the
Temperatureclass from Part II, and confirm both reading.fahrenheitand assigning to it work correctly. - Build the
Shape/Circle/Rectangleclasses from Part III, and confirmShape()on its own raisesTypeError.
Recap
You can now describe encapsulation, abstraction, inheritance, and polymorphism, write your own classes with instance methods, class methods, and static methods, control attribute access with @property, cut boilerplate with @dataclass, define your own exception classes, enforce required methods with abc, and use super() and MRO to reason about multiple inheritance. That’s the full toolkit Module 6 set out to build.
Next lesson: a mini project pulling all of this together into a Library Management System.