And Now You Know Your ABC
How helping at my track club's championships led to Python Abstract Base Classes
I may have already mentioned in a previous post that I’ve rekindled an old passion: track and field athletics. I’m even writing about it in another publication: Back on the Track (look out for the new series on sprint biomechanics, if you’re so inclined!)
This post is inspired by a brief thought that crossed my mind when I considered writing software to assist my club—in the end, I chose not to. (But I already volunteer three hours a week coaching members of the youth team. So no, I don’t feel guilty.)
Here’s the task. I want to create a class to represent a track and field event held in a competition. This entry allows you to enter the raw results as reported by the officials—the athlete’s bib number and the time they clocked in a race. It then computes the event’s full results.
Athletes and Events
A good starting point is to define two classes, Athlete and Event. I’ll focus on the Event class in this post, so I’ll keep the Athlete class fairly basic:

There’s more we could add to make this class more complete. But I won’t. You can create a list of athletes that are competing in the track and field meeting:
To make it easy and efficient to obtain the Athlete object just from a bib number, you can create a bib_to_athlete dictionary:
The dictionary’s keys are the bib numbers as integers and the values are the Athlete objects. Therefore, print(bib_to_athlete[259].name) displays “Carl Lewis”.
Now, let’s move to the Event class:
Let’s go through the data attributes in Event.__init__():
.event_nameis a string with the name of the event. You could use other data types, such as anEnum, but I’ll keep this simple here and use a string..participantsis a list containingAthleteinstances. These are the athletes participating in this particular event at the track and field meeting..resultscontains each athlete’s performance. We have choices. The simplest option is to use a list of tuples, where each tuple contains the athlete and the performance. However, since we’re working in the object-oriented domain, perhaps we could create aResultclass, and.resultsbecomes a list ofResultinstances.
So, let’s create the Result class at the top of the script:
We’ll show an example of Event and Result instances soon. But first we need some methods. Let’s get back to the Event class and define .add_result(), which allows you to add a raw result in the format provided by the officials—the officials only provide the bib number and the performance, such as the time clocked by the athlete:
You add checks for the scenarios when a bib number doesn’t match any athlete in the whole meeting or when the bib number doesn’t match any athlete in the specific event. The dictionary .get() method returns None by default if the key is not present in the dictionary.
If the bib number is valid, you create a Result instance and add it to .results.
Once you add all the results from the officials, you’re ready to finalise the results:
This method sorts the list of results based on the value of the .performance data attribute.
If you’re not familiar with lambda functions, you can read What’s All the Fuss About ‘lambda’ Functions in Python. And to read more about the key parameter in .sort(), see The Key to the ‘key’ Parameter in Python.
Let’s try this class to see everything is as we expect:
You create an Event instance for the 100m sprint race. Then you add three individual results. Finally, you finalise the results to get the athletes and their positions in the race.
Since .finalise_results() sorts based on the performace, the athlete with the fastest time takes the first position in the list, and so on. Here’s the output from the for loop displaying the athletes in .results in order:
Usain Bolt: 9.58
Carl Lewis: 9.86
Jesse Owens: 10.3Usain Bolt is first with the fastest time, followed by Carl Lewis, and Jesse Owens in third. Everything seems to be working well… But “It works” are the two most dangerous words in programming!
It’s Time For The Long Jump
The long jump results come in next from the officials. You know how to input the data and finalise the results now:
And here are the results displayed:
Carl Lewis: 8.87
Mike Powell: 8.95But, but… Mike Powell’s 8.95m is better than Carl Lewis’s 8.87m. The order is wrong!
Of course! In the 100m sprint the fastest time wins, and the fastest time is the lowest number. But in the long jump it’s the longest jump that wins, and that’s the largest number!
The .finalise_results() method no longer works for the long jump or for other field events. For field events, the sorting needs to happen in reverse order.
There are several ways to deal with this problem. There are always several ways to solve a problem in programming.
The simplest is to add an .is_field_event Boolean data attribute in Event and then add an if statement in .finalise_results(). And if this were the only difference between running and field events, this would indeed be a fine option.
However, if there are more differences to account for, the extra fields and if statements scattered throughout the class make the class harder to maintain.
So, let’s look at another option.
Creating Separate Classes for Track Events and Field Events
You could create two classes instead of one: TrackEvent and FieldEvent. Each class will take care of getting the finalise_results() method right and also deal with any other differences we may find between track events and field events.
However, you don’t want to create two unrelated classes since these classes will have a lot in common. Sure, you can copy and paste the code that’s common between the two classes, but you don’t need me to tell you that’s not a good idea. Will you remember to make changes in both places when you decide to change the implementation later?
You also want to make sure they have the same data attribute names for similar things. For example, you don’t want one class to have .finalise_results() and the other .final_results(), say, or change the spelling by mistake. Now, I know you’ll pay attention when creating these methods to make sure this doesn’t happen, but why take the risk?
And when you come back to your code in six months’ time (or a colleague starts working on the code) and you decide you need another class that follows the same principles, will you remember what methods you need to include? You can spend time studying your old classes, of course. But wouldn’t you like to make your life a bit simpler? Of course you would.
Inheritance offers one solution. If you need a refresher on inheritance, you can read the fifth article in the classic Harry Potter OOP series: “You Have Your Mother’s Eyes” • Inheritance in Python Classes.
However, TrackEvent shouldn’t inherit from FieldEvent since a track event is not a field event. The same applies the other way around. These classes are siblings, so they can’t have a parent-child relationship.
Instead, they could both inherit from a common parent. So, let’s keep the Event class as a common parent for both TrackEvent and FieldEvent.
However, the only purpose of Event is to serve as a starting point for the two child classes. You no longer want a user to create instances of Event now that you have TrackEvent and FieldEvent. They should only create instances of TrackEvent or FieldEvent. How can you make this clear in your code and perhaps even prevent users from creating an instance of the parent class, Event?
Abstract Base Classes (ABCs)
The answer is Abstract Base Classes, often shortened to ABCs. The ABC acronym gives the impression these are as easy as ABC—they’re not, but there’s no reason they need to be difficult, either. The title of this article is also a reference to a rhyme my children used to sing when they were toddlers learning their ABC!
Let’s refresh your memory about different terms often used to describe the inheritance relationship between classes:
You can refer to parent class and child class. The child class inherits from the parent class.
Or you can refer to superclass and subclass. The subclass inherits from the superclass. This is where the built-in
super()gets its name.Or you can refer to base class and derived class. The derived class inherits from the base class.
Whatever terms you choose to use, they refer to the same things!
So, an ABC is a base class, since other classes are derived from it. That deals with the BC in ABC. And it’s abstract because you’re never going to create a concrete instance of this class.
You’re not meant to create an instance of an abstract base class and often you’ll be prevented from doing so. But you can use it to derive other classes.
Let’s turn Event into an abstract base class:
The changes from the previous version are highlighted. Let’s go through each change:
From the
abcmodule—and now you know what these letters stand for—you importABCandabstractmethod. You’ll see both of these referred to and explained in the bullets below.When you define the
Eventclass, you includeABCas its base or parent class. When a class inherits from theABCclass, the class itself becomes an abstract base class. Anyone reading your code will immediately understand the role of this class.You add the
@abstractmethoddecorator before defining.finalise_results()and you also remove the contents of this method. Since you can’t leave the body of a function or method blank, you include thepassstatement as the method’s body. This method doesn’t do anything, but it’s there, and it’s marked as an abstract method. Let’s see what this means…
Here’s what you’re effectively stating when you create this ABC with the .finalise_results() abstract method: Any class derived from the Event ABC must include a .finalise_results() method.
Let’s explore this by defining TrackEvent and FieldEvent. At first, you’ll keep them simple:
You define the two new classes TrackEvent and FieldEvent. They inherit from Event, which is an abstract base class. You just include pass as the body of each class for now. This means that these classes inherit everything from the parent class and have no changes or additions. They’re identical to the parent class.
Note that you now use TrackEvent and FieldEvent to create instances for the 100m race and the long jump.
However, you get an error when you try to create these instances:
Traceback (most recent call last):
File ... line 53, in <module>
track_100m = TrackEvent(
“100m Sprint”,
[bib_to_athlete[259], bib_to_athlete[161], bib_to_athlete[362]]
)
TypeError: Can’t instantiate abstract class TrackEvent without
an implementation for abstract method ‘finalise_results’The abstract base class includes the method .finalise_results(), which is marked as an abstract method. Python is expecting a concrete implementation of this method. Any class that inherits from the Event ABC must have a concrete implementation of this method. Let’s fix this:
In TrackEvent, you include the same code you had in the original Event.finalise_results() since this algorithm works well for track events where the smallest numbers (the fastest times) represent the best performances.
However, you pass reverse=True to .sort() in FieldEvent.finalise_results() since the largest numbers (longest distances) represent the best performances in this case.
You can now try these new classes on the 100m race results and the long jump results you used earlier:
You now use the new derived classes TrackEvent and FieldEvent in this code instead of Event. You also add two new printouts to separate the results.
Here’s the output now:
100m Sprint Results:
Usain Bolt: 9.58
Carl Lewis: 9.86
Jesse Owens: 10.3
Long Jump Results:
Mike Powell: 8.95
Carl Lewis: 8.87The 100m results show the fastest times (smallest values) as the best performances. The long jump results show the longer jump (larger value) as the best performance. All as it should be!
Wind Readings and Breaking Ties
But there are more differences we need to account for. In some track and field events, the wind reading matters. In these events, if the wind reading is larger than 2.0 m/s, the results still stand but the performances cannot be used for official records.
But does this affect track events or field events? So should you account for this in the TrackEvent class or in the FieldEvent class?
It’s not so simple. Wind readings matter in some track events, but not all. And they also matter in some field events, but not all. So, you have a different subset of events to account for now. The 100m, 200m, 110m hurdles and 100m hurdles, which are all track events, are in the same category as the long jump and triple jump, which are field events.
But before we find a solution for this, here’s something else to mess things up even more. What happens when there’s a tie—when two athletes have the same performance value? The tie-breaking rules also depend on the event. Let’s ignore the track events here, since depending on what timing system is used, it’s either the officials who decide or the higher precision times from the automatic timing systems.
But what about the field events? In most of them, if there’s a tie, the next best performance is taken into account. However, the rules are different for the high jump and pole vault events where there’s a count back system used. Explaining the rules of track and field is not the purpose of this article, so I won’t!
So that’s yet another subset to consider: tie breaks in the vertical jumps are different from tie breaks in the horizontal jumps and throws.
How can we account for all these subsets of events?
To ABC or Not To ABC
There are always many solutions to the same problem. You can extend the idea of using ABCs to cater for all options. But the Venn diagram of which event falls under which category is a bit complicated in this case.
The 100m, 200m, 100m hurdles and 110m hurdles are all track events affected by wind readings. But the long jump and triple jump are also affected by wind readings but they’re field events. The discus and other throw events are field events—so the longest throw wins—but aren’t affected by high wind readings. And the long jump, triple jump, and the throws have a next-best jump/throw tie-breaking rule. But the pole vault and high jump are field events not affected by the wind but with different tie-breaking rules.
Are you still with me? Confused? Can you think of an abstract base class structure to account for all these combinations. It’s not impossible, but you’ll need several layers in the hierarchy.
Instead, I’ll explore a different route in a second article, which I’ll publish soon!
The follow-up article will be part of The Club here at The Python Coding Stack. These are the articles for paid subscribers. So, if you’d like to read about a different way—arguably, a better way—of merging all these requirements into our classes, join The Club by upgrading to a paid subscription.
Final Words for This Article • Ready for Part 2?
Inheritance is a great tool. And abstract base classes enable you to organise your hierarchies by creating common base classes for concrete classes you may need to define.
However, inheritance hierarchies can get quite complex. Since inheritance provides a tight coupling between classes, deep hierarchies can cause a bit of a headache.
Still, abstract base classes provide a great tool to make code cleaner, more readable, and more robust. In the follow-up to this article (coming soon), we’ll look at another tool you can use along with inheritance to solve these complex relationships. So join The Club to carry on reading about the track and field classes and how mixins and composition can help manage the complexity.
Code in this article uses Python 3.14
The code images used in this article are created using Snappify. [Affiliate link]
Join The Club, the exclusive area for paid subscribers for more Python posts, videos, a members’ forum, and more.
You can also support this publication by making a one-off contribution of any amount you wish.
For more Python resources, you can also visit Real Python—you may even stumble on one of my own articles or courses there!
Also, are you interested in technical writing? You’d like to make your own writing more narrative, more engaging, more memorable? Have a look at Breaking the Rules.
And you can find out more about me at stephengruppetta.com
Further reading related to this article’s topic:
Appendix: Code Blocks
Code Block #1
class Athlete:
def __init__(self, name, bib_number):
self.name = name
self.bib_number = bib_number
Code Block #2
# ...
list_of_athletes = [
Athlete(”Carl Lewis”, 259),
Athlete(”Jesse Owens”, 161),
Athlete(”Usain Bolt”, 362),
Athlete(”Mike Powell”, 412),
Athlete(”Florence Griffith-Joyner”, 263),
Athlete(”Allyson Felix”, 298),
Athlete(”David Rudisha”, 177),
# ... Add more athletes as needed
]
Code Block #3
# ...
bib_to_athlete = {
athlete.bib_number: athlete for athlete in list_of_athletes
}
Code Block #4
# ...
class Event:
def __init__(self, event_name, participants):
self.event_name = event_name
self.participants = participants
self.results = []
Code Block #5
class Result:
def __init__(self, athlete, performance):
self.athlete = athlete
self.performance = performance
# ...
Code Block #6
# ...
class Event:
# ...
def add_result(self, bib_number, performance):
athlete = bib_to_athlete.get(bib_number)
if not athlete or athlete not in self.participants:
raise ValueError(f”Invalid bib number {bib_number}”)
self.results.append(
Result(athlete, performance)
)
Code Block #7
# ...
class Event:
# ...
def finalise_results(self):
self.results.sort(key=lambda item: item.performance)
Code Block #8
# ...
track_100m = Event(
“100m Sprint”,
[bib_to_athlete[259], bib_to_athlete[161], bib_to_athlete[362]]
)
track_100m.add_result(259, 9.86)
track_100m.add_result(161, 10.3)
track_100m.add_result(362, 9.58)
track_100m.finalise_results()
for result in track_100m.results:
print(f”{result.athlete.name}: {result.performance}”)
Code Block #9
# ...
field_long_jump = Event(
“Long Jump”,
[bib_to_athlete[412], bib_to_athlete[259]]
)
field_long_jump.add_result(412, 8.95)
field_long_jump.add_result(259, 8.87)
field_long_jump.finalise_results()
for result in field_long_jump.results:
print(f”{result.athlete.name}: {result.performance}”)
Code Block #10
from abc import ABC, abstractmethod
class Result:
def __init__(self, athlete, performance):
self.athlete = athlete
self.performance = performance
class Athlete:
def __init__(self, name, bib_number):
self.name = name
self.bib_number = bib_number
class Event(ABC):
def __init__(self, event_name, participants):
self.event_name = event_name
self.participants = participants
self.results = []
def add_result(self, bib_number, performance):
athlete = bib_to_athlete.get(bib_number)
if not athlete or athlete not in self.participants:
raise ValueError(f”Invalid bib number {bib_number}”)
self.results.append(
Result(athlete, performance)
)
@abstractmethod
def finalise_results(self):
pass
# ...
Code Block #11
# ...
class TrackEvent(Event):
pass
class FieldEvent(Event):
pass
# ...
track_100m = TrackEvent(
“100m Sprint”,
[bib_to_athlete[259], bib_to_athlete[161], bib_to_athlete[362]]
)
# ...
field_long_jump = FieldEvent(
“Long Jump”,
[bib_to_athlete[412], bib_to_athlete[259]]
)
# ...
Code Block #12
# ...
class TrackEvent(Event):
def finalise_results(self):
self.results.sort(key=lambda item: item.performance)
class FieldEvent(Event):
def finalise_results(self):
self.results.sort(
key=lambda item: item.performance,
reverse=True,
)
# ...
Code Block #13
# ...
track_100m = TrackEvent(
“100m Sprint”,
[bib_to_athlete[259], bib_to_athlete[161], bib_to_athlete[362]]
)
track_100m.add_result(259, 9.86)
track_100m.add_result(161, 10.3)
track_100m.add_result(362, 9.58)
track_100m.finalise_results()
print(”100m Sprint Results:”)
for result in track_100m.results:
print(f”{result.athlete.name}: {result.performance}”)
field_long_jump = FieldEvent(
“Long Jump”,
[bib_to_athlete[412], bib_to_athlete[259]]
)
field_long_jump.add_result(412, 8.95)
field_long_jump.add_result(259, 8.87)
field_long_jump.finalise_results()
print(”\nLong Jump Results:”)
for result in field_long_jump.results:
print(f”{result.athlete.name}: {result.performance}”)
For more Python resources, you can also visit Real Python—you may even stumble on one of my own articles or courses there!
Also, are you interested in technical writing? You’d like to make your own writing more narrative, more engaging, more memorable? Have a look at Breaking the Rules.
And you can find out more about me at stephengruppetta.com















