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 Dispossessed", "Ursula K LeGuin"), 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 search in self.title or search 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 search in self.title or search in self.director or any([search 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, "LeGuin")
. See the
lecture capture for details.
Inheritance
Right now, we’re considering each class to be totally separate, with no shared
code or data. For instance, even though Book~s and ~Movie~s both implement a
~matches
method, the two methods do are have completely different
implementations. Inheritance gives us a way to share code between
objects. Classes can inherit from other classes, like this:
class A: pass class B(A): pass
When there’s an inheritance relationship like this between two classes, we sat
that A
is the superclass of B
and B
is a subclass of A
.
Let’s see an example of inheritance in action. Let’s say our library wants to
track which items have been checked out, and only return items in a search if
they are actually available. First, we’ll implement a LibraryItem
class, which
we’ll use as the superclass for both books and movies:
class LibraryItem: def __init__(self): self.checked_out = False def checkout(self): self.checked_out = True def return(self): self.checked_out = True
The LibraryItem
class only handles checking items out and back in–it doesn’t
know anything about what the items actually are.
In order to use our new superclass, we’ll make some changes to the Book
and
Movie
classes:
class Book(LibraryItem): def __init__(self, title: str, author: str): self.title = title self.author = author super().__init__(self) class Movie(LibraryItem): def __init__(self, title: str, director: str, actors: list): self.title = title self.director = director self.actors = actors super().__init__(self)
Both Book
and Movie
objects will now have a checked_out
field, as well as
the checkout
and return
methods. We can use the field in each class’s
matches
method:
class Book(LibraryItem): def matches(self, query: str) -> bool: return (not self.checked_out) and (search in self.title or search in self.author) class Movie(LibraryItem): def matches(self, query: str) -> bool: return (not self.checked_out) and search in self.title or search in self.director or any([search in actor for actor in self.actors])