Polymorphism

Polymorphism

Polymorphism (poly- multiple, morph- shape), is the ability to write code that works over multiple data types. It’s very easy to write polymorphic functions in Python–for instance, what type does the following function return?

def add(x, y):
  return x + y

Depending on the type of its arguments, it could return an integer, a float, or a string. This property can be confusing–if we only plan on calling this function with a specific type, we might want to add annotations:

def add(x: str, y: str) -> str:
  return x + y

Polymorphic code can also be useful, though, as we’ll see below.

Polymorphism at the library

Let’s revisit an example we saw in CSCI 0111: tracking items at a library. The library has both books and movies, and we can make classes for both of them:

class Book:
    def __init__(self, title: str, author: str):
        self.title = title
        self.author = author

class Movie:
    def __init__(self, title: str, director: str, actors: list):
        self.title = title
        self.director = director
        self.actors = actors

We can represent a library as a list of these items:

library = [Book("The Nickel Boys", "Colson Whitehead"),
           Movie("Parasite", "Bong Joon-ho", ["Song Kang-ho", "Lee Sun-kyun"])]

An operation we might want on libraries is text search over library items. An advanced search might allow users to search over authors or directors, or search for particular item types (books or movies). In Pyret we implemented this by making books and movies constructors of the same datatype. We could do a similar thing in Python, but there’s a more elegant solution.

We’ll add a method to each class:

class Book:
    def __init__(self, title: str, author: str):
        self.title = title
        self.author = author

    def matches(self, query: str) -> bool:
        return query in self.title or query in self.author

class Movie:
    def __init__(self, title: str, director: str, actors: list):
        self.title = title
        self.director = director
        self.actors = actors

    def matches(self, query: str) -> bool:
        return query in self.title or query in self.director or 
          any([query in actor for actor in self.actors])

Then we can implement our search function:

def search(library: list, query: str) -> list:
    return [item for item in library if item.matches(query)]

Let’s work though what happens when we call search(library, "Colson"). See the lecture capture for details.

Project 2

We talked about the second project. See the lecture capture for details.

Polymorphism example: Python’s special methods

Python uses polymorphism ubiquitously. For example, recall that dataclass instances printed out in a nice way at the Python console. How can we get similar behavior with our own classes?

We can implement the __repr__ method:

class Book:
    # other methods...

    def __repr__(self):
        return self.title + " by " self.author

Then if we view a book at the console:

>>> library[0]
The Nickel Boys by Colson Whitehead

When we view an object at the console, Python calls the __repr__ (short for “representation”) method internally to decide what to display. Note that this only affects the way Python displays the object–it doesn’t change anything about the object’s internal representation.