\( \newcommand{\NOT}{\neg} \newcommand{\AND}{\wedge} \newcommand{\OR}{\vee} \newcommand{\XOR}{\oplus} \newcommand{\IMP}{\Rightarrow} \newcommand{\IFF}{\Leftrightarrow} \newcommand{\TRUE}{\text{True}\xspace} \newcommand{\FALSE}{\text{False}\xspace} \newcommand{\IN}{\,{\in}\,} \newcommand{\NOTIN}{\,{\notin}\,} \newcommand{\TO}{\rightarrow} \newcommand{\DIV}{\mid} \newcommand{\NDIV}{\nmid} \newcommand{\MOD}[1]{\pmod{#1}} \newcommand{\MODS}[1]{\ (\text{mod}\ #1)} \newcommand{\N}{\mathbb N} \newcommand{\Z}{\mathbb Z} \newcommand{\Q}{\mathbb Q} \newcommand{\R}{\mathbb R} \newcommand{\C}{\mathbb C} \newcommand{\cA}{\mathcal A} \newcommand{\cB}{\mathcal B} \newcommand{\cC}{\mathcal C} \newcommand{\cD}{\mathcal D} \newcommand{\cE}{\mathcal E} \newcommand{\cF}{\mathcal F} \newcommand{\cG}{\mathcal G} \newcommand{\cH}{\mathcal H} \newcommand{\cI}{\mathcal I} \newcommand{\cJ}{\mathcal J} \newcommand{\cL}{\mathcal L} \newcommand{\cK}{\mathcal K} \newcommand{\cN}{\mathcal N} \newcommand{\cO}{\mathcal O} \newcommand{\cP}{\mathcal P} \newcommand{\cQ}{\mathcal Q} \newcommand{\cS}{\mathcal S} \newcommand{\cT}{\mathcal T} \newcommand{\cV}{\mathcal V} \newcommand{\cW}{\mathcal W} \newcommand{\cZ}{\mathcal Z} \newcommand{\emp}{\emptyset} \newcommand{\bs}{\backslash} \newcommand{\floor}[1]{\left \lfloor #1 \right \rfloor} \newcommand{\ceil}[1]{\left \lceil #1 \right \rceil} \newcommand{\abs}[1]{\left | #1 \right |} \newcommand{\xspace}{} \newcommand{\proofheader}[1]{\underline{\textbf{#1}}} \)

10.10 The object Superclass

In our very first chapter, we described every piece of data as an object, and have continued to use this term throughout this course. It turns out that “object” is not merely a theoretical concept, but made explicit in the Python language. Python has a special class called object, which is an ancestor classBy “ancestor” we mean either a parent class, or a parent of a parent class, etc. of every other class: built-in classes like int, our custom data classes and the classes we’ve defined in this chapter, including the abstract Stack class.

By default, whenever we define a new class (including data classes), if we do not specify a superclass in parentheses, object is the implicit superclass, which is why we can write class Stack: instead of class Stack(object):.

The object special methods

This object class defines several special methods as part of its shared public interface, including two we have seen already this chapter: The Python convention is to name methods that have a special purpose with double underscores. These are sometimes called “dunder” methods (short for double underscore).

Method inheritance

Unlike our Stack abstract class earlier this chapter, the object class is actually not abstract, and implements each of these methods. We can use this to illustrate a different form of inheritance, where the superclass is a concrete class. In this case, inheritance is used not just to define a shared public interface, but also to provide default implementations for each method in the interface.

For example, suppose we create a dummy class with a completely empty body:

class Donut:
    """A donut, because why not?"

This class inherits the object.__init__ method, which allows us to create new Donut instances.

>>> donut = Donut()
>>> type(donut)
<class '__main__.Donut'>

Similarly, this class inherits the object.__str__ method, which returns a string that states the class name and memory location of the object:

>>> d = Donut()
>>> d.__str__()
'<__main__.Donut object at 0x7fc299d7b588>'

We can use the built-in dir function to see all of the special methods that Donut has inherited from object:Though this list includes few special attributes set directly by the Python interpreter, which are beyond the scope of this course.

>>> dir(Donut)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

There is another reason these methods are special beyond simply being inherited from the object superclass: they are often called by other functions or parts of Python syntax. For example, we have already seen how the __init__ method is called when a new object is initialized.

The __str__ method is called when we attempt to convert an object to a string by calling str on it:

>>> d = Donut()
>>> d.__str__()
'<__main__.Donut object at 0x7fc299d7b588>'
>>> str(d)
'<__main__.Donut object at 0x7fc299d7b588>'

Similarly, the built-in print function actually first converts its arguments into strings using their __str__ methods, and then prints out the resulting text.

Method overriding

Now, even though the object superclass contains default implementations of __init__ and __str__, in practice we often want to define our own custom implementations of these methods.

Every time we’ve defined our own __init__ in a class, we have overridden the object.__init__ method. Formally, we say that a class C overrides a method m when the method m is defined in the superclass of C, and is also given a concrete implementation in the body of C. This definition applies whether the superclass of C has m as an abstract or concrete method. For example, we could say that Stack1 overrides the push and pop method from its abstract superclass Stack.

Similarly, when we defined a custom exception class in 10.6 Exceptions as a Part of the Public Interface,

class EmptyStackError(Exception):
    """Exception raised when calling pop on an empty stack."""

    def __str__(self) -> str:
        """Return a string representation of this error."""
        return 'pop may not be called on an empty stack'

this class overrode the __str__ method to use its own string representation, which is displayed when this exception is raised.