Python Abstract Classes Examples
Short, runnable programs that show python abstract classes (abc) in action. Read the explanation, press Run, then change the code and try your own ideas. New to the topic? Start with the Python Abstract Classes (ABC) lesson.
1See what is still missing with __abstractmethods__
Every abstract class keeps a frozenset of the method names that still have no implementation. A child that fills in only part of the contract shrinks that set but stays abstract until it is empty. Sorting the names keeps the output stable.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
class HalfDone(Shape):
def area(self):
return 0
print("Shape still needs:", sorted(Shape.__abstractmethods__))
print("HalfDone still needs:", sorted(HalfDone.__abstractmethods__))
try:
HalfDone()
except TypeError:
print("HalfDone cannot be created yet")Expected outputShape still needs: ['area', 'perimeter']
HalfDone still needs: ['perimeter']
HalfDone cannot be created yet
2One checkout, many payment methods
checkout() only knows the PaymentMethod contract, so it works with every child, including ones that need their own data such as a card number.
from abc import ABC, abstractmethod
class PaymentMethod(ABC):
@abstractmethod
def pay(self, amount):
"""Return a receipt line."""
class Card(PaymentMethod):
def __init__(self, last4):
self.last4 = last4
def pay(self, amount):
return f"${amount:.2f} charged to card ending {self.last4}"
class Cash(PaymentMethod):
def pay(self, amount):
return f"${amount:.2f} paid in cash"
def checkout(total, method):
return method.pay(total)
for method in [Card("4242"), Cash()]:
print(checkout(19.5, method))Expected output$19.50 charged to card ending 4242
$19.50 paid in cash
3Template method with an optional hook
prepare() fixes the recipe. brew() is abstract, so every drink must define it, while wants_milk() is a hook: a concrete method with a sensible default that children may override.
from abc import ABC, abstractmethod
class Beverage(ABC):
def prepare(self):
extra = " > add milk" if self.wants_milk() else ""
return f"boil water > {self.brew()} > pour{extra}"
@abstractmethod
def brew(self):
pass
def wants_milk(self):
return False
class Tea(Beverage):
def brew(self):
return "steep tea bag"
class Latte(Beverage):
def brew(self):
return "press espresso"
def wants_milk(self):
return True
print(Tea().prepare())
print(Latte().prepare())Expected outputboil water > steep tea bag > pour
boil water > press espresso > pour > add milk
4An abstract property satisfied by a class attribute
The abstract property yearly_rate only demands that the name is replaced by something real. Checking uses a property; Savings gets away with a plain class attribute. The shared method after_one_year() works with both.
from abc import ABC, abstractmethod
class Account(ABC):
def __init__(self, balance):
self.balance = balance
@property
@abstractmethod
def yearly_rate(self):
"""Interest rate as a fraction, such as 0.02."""
def after_one_year(self):
return round(self.balance * (1 + self.yearly_rate), 2)
class Savings(Account):
yearly_rate = 0.03
class Checking(Account):
@property
def yearly_rate(self):
return 0.001
print(Savings(1000).after_one_year())
print(Checking(1000).after_one_year())Expected output1030.0
1001.0
5Fail early with an ABC, fail late with duck typing
Both Printer and BrokenWriter forgot write(). The duck-typed Printer happily becomes an object and only breaks when write() is called. The abstract BrokenWriter is stopped the moment you try to create it.
from abc import ABC, abstractmethod
class Printer:
pass
class Writer(ABC):
@abstractmethod
def write(self, text):
pass
class BrokenWriter(Writer):
pass
def save(writer, text):
writer.write(text)
for label, make in [("duck typing", Printer), ("abstract class", BrokenWriter)]:
try:
obj = make()
print(label, "-> object created")
save(obj, "hello")
except (AttributeError, TypeError):
print(label, "-> failed")Expected outputduck typing -> object created
duck typing -> failed
abstract class -> failed
6An abstract method can have code that children reuse
The body of an abstract method is never used automatically, but a child that overrides it can still call it with super(). This is handy for shared clean-up code that every child must extend.
from abc import ABC, abstractmethod
class Formatter(ABC):
@abstractmethod
def format(self, text):
"""Children must override this, and may reuse this base code."""
return text.strip()
class EmailFormatter(Formatter):
def format(self, text):
cleaned = super().format(text)
return f"Email: {cleaned}"
print(EmailFormatter().format(" Order shipped "))Expected outputEmail: Order shipped
Practise next
Python Abstract Classes (ABC) exercises → Back to the lesson
Python Inheritance examplesPython Polymorphism and Duck Typing examplesPython Composition: Has-A vs Is-A examples