I’ve grown tired of the common polymorphism example classes and think that they do polymorphism a disservice because they don’t demonstrate the real power of virtual dispatch. For example:

class Animal():
    def sound(self):
        raise NotImplementedError()


class Dog(Animal):
    def sound(self):
        return 'woof'


class Cat(Animal):
    def sound(self):
        return 'meow'


class Fox(Animal):
    def sound(self):
        return 'ring-ding-ding-ding-dingeringeding'


for animal in [Dog(), Cat(), Fox()]:
    print(animal.sound())

I’ll spare you the example implementation but also remember the classic Car base class with abstract wheelCount() and doorCount() methods for derived classes to implement.

A programmer might understand how these classes behave and reason through code using them but still not understand when polymorphism should be used. In fact, this is a bad use of polymorphism. A better implementation would merely have a single class with member variables to capture the relevant information along with factory functions to create common instances of them:

class Animal():
    def __init__(self, sound):
        self.sound = sound

    def sound(self):
        return self.sound


def Dog():
    return Animal('woof')


def Cat():
    return Animal('meow')


def Fox():
    return Animal('ring-ding-ding-ding-dingeringeding')


for animal in [Dog(), Cat(), Fox()]:
    print(animal.sound())

An enum with singletons for the necessary instances would also be a good choice but you get the idea.

So what is a good example of polymorphism? Something that actually requires different code to run rather than only different data:

class ChatChannel():
    def send_message(self, message):
        raise NotImplementedError()


import slack


class SlackChannel(ChatChannel):
    def __init__(self, channel):
        self.channel = channel

    def send_message(self, message):
        slack.send_message(self.channel, message)


import hipchat


class HipChatChannel(ChatChannel):
    def __init__(self, channel):
        if channel.startswith('@'):
            self.is_user = True
            self.channel = channel.lstrip('@')
        else:
            self.is_user = False
            self.channel = channel.lstrip('#')

    def send_message(self, message):
        if self.is_user:
            hipchat.send_user_message(self.channel, message)
        else:
            hipchat.send_channel_message(self.channel, message)


channels = [
    SlackChannel('#general'),
    SlackChannel('@alice'),
    HipChatChannel('#random'),
    HipChatChannel('@bob'),
]

for channel in channels:
    channel.send_message('Hello World!')

This example has a ChatChannel class which is implemented by SlackChannel and HipChatChannel. SlackChannel uses the slack library to send a message while HipChatChannel uses the hipchat library and has some special handling for users and channels. The different library dependencies and logic is something that you can’t do with pure data like in the revised Animal example. You could do it by passing in a lambda, storing it as a member variable, and calling that to send the message. However, that’s just hand rolled virtual dispatch at which point you’re better off using proper inheritance for it.

All of this is not to say that the Animal example shouldn’t be used at all! It can be useful to demonstrate the concept in a very basic way but the conversation should quickly move to more motivational examples to help explain when polymorphism should and should not be used.