CodingNic

Object Oriented Programming

Special Methods and Polymorphism

Object Oriented Programming 20 min read

Special Methods and Polymorphism

Objectives

By the end of this chapter, you should be able to:

  • Recognize common special (“dunder”) methods
  • Explain polymorphism and give an example
  • Use abc and @abstractmethod to enforce that a subclass implements a method
  • Extend a built-in type with a custom method

💡 Why this matters: Special methods are how your own classes plug into Python’s built-in syntax (print(), len(), +, and more) instead of needing their own one-off functions.

Special (“Dunder”) Methods

Methods surrounded by double underscores, called dunder methods, are how Python hooks your class into its built-in syntax and functions. A few of the most common:

Method Purpose
__init__ Runs when an instance is created
__str__ Controls what str(obj) and print(obj) display
__repr__ A developer-facing representation, similar in spirit to __str__
__len__ Controls what len(obj) returns
__del__ Runs when an instance is about to be destroyed
__doc__ The docstring associated with a class
__class__ A reference to the instance’s class
__format__ Controls how an instance is rendered by format()

You’ll encounter more of these as you read other people’s Python code. Implementing them is what lets a custom class behave like a built-in one.

Polymorphism

Polymorphism means the same operation behaves differently depending on the type it’s applied to. You’ve already used this without naming it:

python
5 + 3      # 8, numeric addition
"8" + "3"  # "83", string concatenation

+ does something different for numbers than it does for strings: that’s polymorphism. len() is another example:

python
sample_list = [1, 2, 3, 4]
sample_tuple = (1, 2, 3)
sample_string = "hello"

len(sample_list)    # 4
len(sample_tuple)   # 3
len(sample_string)  # 5

One len() function, three different behaviors depending on the type it receives. Polymorphism also shows up between related classes: two classes don’t need a common parent to both implement the same method name:

python
class Pet:
    def talk(self):
        raise NotImplementedError("Subclasses must implement talk()")


class Dog(Pet):
    def talk(self):
        return "WOOF!"


class Cat(Pet):
    def talk(self):
        return "MEOW!"

Both Dog and Cat implement .talk() differently, but any code that calls .talk() on a Pet doesn’t need to know which subclass it actually has. It just works, which is the whole point of polymorphism.

Formalizing It with Abstract Base Classes

Pet above works, but nothing stops someone from instantiating Pet directly, or forgetting to override .talk() in a new subclass. You’d only find out at runtime, when NotImplementedError actually fires. The abc module makes that contract explicit and enforced, using ABC as a base class and @abstractmethod to mark required methods:

python
from abc import ABC, abstractmethod

class Pet(ABC):
    @abstractmethod
    def talk(self):
        pass


class Dog(Pet):
    def talk(self):
        return "WOOF!"


class Cat(Pet):
    def talk(self):
        return "MEOW!"
python
Pet()  # TypeError: Can't instantiate abstract class Pet with abstract method talk

class Fish(Pet):
    pass

Fish()  # TypeError: Can't instantiate abstract class Fish with abstract method talk

An ABC subclass with an unimplemented @abstractmethod simply can’t be instantiated at all. Python catches the mistake immediately, at the moment you try to create the object, rather than later when something finally calls .talk() on it. Dog and Cat work exactly as before, since both actually implement talk().

Use plain NotImplementedError for a quick, informal version of this pattern; reach for ABC/@abstractmethod when you want Python itself to enforce that a subclass can’t skip a required method.

Extending a Built-in Type

You can also subclass a built-in type directly, to add your own methods on top of everything it already does:

python
class ExtendedStr(str):
    def first_last_character(self):
        return self[0] + self[-1]

s = ExtendedStr("awesome")
s.first_last_character()  # "ae"
len(s)                     # 7, still behaves like a normal string
s.upper()                  # "AWESOME", inherited straight from str

ExtendedStr is a real subclass of str, so it inherits every normal string method (.upper(), len(), and the rest) and adds first_last_character() on top. Note that a plain string literal like "awesome" is still an ordinary str, not an ExtendedStr, so it does not get the new method:

python
"awesome".first_last_character()  # AttributeError: 'str' object has no attribute 'first_last_character'

You have to actually construct an ExtendedStr to get the extra method. Subclassing a built-in type like this is a real technique, but it’s an approach to reach for sparingly, since most code that expects a plain str neither knows nor cares about your extra method.

Try It

  1. Add a __str__ method to a class of your own and confirm that print() uses it.
  2. Write two unrelated classes that both implement a method with the same name, and call that method on an instance of each without changing your calling code.
  3. Predict what len() returns for a dictionary before trying it, then check.
  4. Turn one of your own classes into an ABC with an @abstractmethod, then confirm you can’t instantiate it directly until a subclass implements that method.

Recap

  • Dunder methods (__init__, __str__, __len__, and others) are how your class plugs into Python’s built-in syntax and functions.
  • Polymorphism means the same operation (an operator, a function, a method name) behaves differently depending on the type or class it’s used with.
  • Polymorphism doesn’t require inheritance; unrelated classes can each implement a method with the same name.
  • ABC and @abstractmethod enforce that a subclass implements a required method. Python refuses to instantiate a class that doesn’t, rather than failing later at the call site.
  • You can subclass a built-in type to add new methods, though it’s an approach to use sparingly.

Next lesson: put it all together with object-oriented programming exercises.