\( \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.3 Defining Our Own Methods

It is certainly possible to accomplish everything that we would ever want to do with our Person class from the previous section by writing top-level functions, and this is the approach we’ve taken with data classes up to this point. An alternate and commonly-used approach is to define methods for a data type, which become part of the interface of that data type. Remember that methods are just functions that belong to a data type—but this “belonging to” is not just a conceptual relationship! Defining and using methods have concrete requirements and consequences in Python, and software engineering in general. When we define a data class and top-level functions, the interface of a data class itself only consists of its attributes; we have to remember to import those functions separately in order to use them. When we define a class with methods, those methods are always bundled with the class, and so any instance of the class can use those methods, without needing to import them separately.

Defining a method: an example

We have seen one example of a method definition already: the initializer, __init__. More generally, any function that operates on an instance of a class can be converted into a method by doing the following:

For example, suppose we had the following function to increase a person’s age:

def increase_age(person: Person, years: int) -> None:
    """Add the given number of years to the given person's age.

    >>> david = Person('David', 'Liu', 100, '40 St. George Street')
    >>> increase_age(david, 10)
    >>> david.age
    110
    """
    person.age = person.age + years

We can turn increase_age into a Person method as follows:

class Person:
    """A custom data type that represents data for a person."""
    given_name: str
    family_name: str
    age: int
    address: str

    def __init__(self, given_name: str, family_name: str, age: int, address: str) -> None:
        """Initialize a new Person object."""
        self.given_name = given_name
        self.family_name = family_name
        self.age = age
        self.address = address

    def increase_age(self, years: int) -> None:
        """Add the given number of years to this person's age.

        >>> david = Person('David', 'Liu', 100, '40 St. George Street')
        >>> david.increase_age(10)
        >>> david.age
        110
        """
        self.age = self.age + years

Notice that we now use parameter self (without a type annotation) to access instance attributes, just as we did in the initializer. In our function docstring, the phrase “the given person” changes to “this person”, We typically use the word “this” in a method docstring to refer to the object instance that self refers to. In fact, some other programming languages also use this instead of self as a variable or keyword to refer to this object in code. and our doctest example changes the call to increase_age to Person.increase_age.

Defining a method: general syntax

In general, Python uses the following syntax for defining a method:

class <ClassName>:
    """..."""
    <instance attributes/types omitted>

    def <method_name>(self, <param>: <type>, ...) -> <return type>:
        """Method docstring"""
        <statement>
        ...