Methods

Recall that a method is an attribute of a class that is a function. For example, “append” is a method that is defined for the list class and “capitalize” is a method of the str (string) class.

# create an instance of the `list` class/type
# and invoke the instance method `append`
>>> a = [1, 2, 3]
>>> a.append(-1)
>>> a
[1, 2, 3, -1]

# create an instance of the `str` class/type
# and invoke the instance method `capitalize`
>>> b = "moo"
>>> b.capitalize()
'Moo'

Here we will encounter three varieties of methods:

  • instance methods

  • class methods

  • static methods

whose differences are relatively minor but are important to understand. The functions “append” and “capitalize” are both examples of instance methods, specifically, as they are designed to be invoked by a particular list instance and string instance, respectively.

We have already worked with the instance method __init__, which is special in that it is reserved by Python to be executed whenever class-initialization is invoked. Similarly, the special instance method __add__ informs how an object interacts with the + operator. For example, float.__add__ specifies that + will sum the values of float instances, whereas list.__add__ specifies that + will concatenate list instances together. We will conclude our discussion of methods by surveying a number of these special methods - they will greatly bolster our ability to define convenient, user-friendly classes.

Instance Methods

An instance method is defined whenever a function definition is specified within the body of a class. This may seem trivial but there is still a significant nuance that must be cleared up, which is that ‘self’ is the defacto first-argument for any instance method. This is something that we encountered when working with __init__. Let’s proceed naively so that we will hit a very common error, which will bring this matter to light. We begin by creating a class with an instance method that simply accepts one argument and then returns that argument unchanged:

class Dummy:
    def func(x):
        """ An instance method that returns `x` unchanged.
            This is a bad version of this instance method!"""
        return x

We can call this method from the class object Dummy itself, and it will behave as-expected:

>>> Dummy.func(2)
2

but something strange happens when we try to call func from an instance of Dummy:

# calling `func` from an instance of `Dummy` produces
# an unexpected error
>>> inst = Dummy()
>>> inst.func(2)
TypeError: func() takes 1 positional argument but 2 were given

At first glance, this error message doesn’t seem to make any sense. It is indeed true that func only accepts one argument - we specified that it should accept the argument x in its function definition. How is it that inst.func(2) specifies two arguments? It seems like we are solely passing 2 to our method. Herein lies an extremely important detail:

Important!

When you call an instance method (e.g. func) from an instance object (e.g. inst), Python automatically passes that instance object as the first argument, in addition to any other arguments that were passed in by the user.

So according to this, inst is being passed as the argument x and we are attempting to pass 2 as a second argument to the method; this explains the error message complaining about passing func two arguments. By this logic we should be able to call a.func() and see that inst is being passed as the argument x - recall that func is defined to simply return x unchanged. Let’s confirm this:

# verifying that `inst` is being passed as the first argument
# of the instance-method `func`

# note the memory address of the Dummy-instance `inst`
>>> inst
<__main__.Dummy at 0x284f0008da0>

# `inst.func()` automatically receives `inst` as the
# input argument, which is then returned unchanged
>>> inst.func()
<__main__.Dummy at 0x284f0008da0>

# `inst` is indeed being passed to, and
# returned by, `func`
>>> out = inst.func()
>>> inst is out
True

Note that this “under the hood” behavior only occurs when the method is being called from an instance; this is why we didn’t face this issue when invoking func from Dummy - Dummy is a class object, not an instance. Thus, inst.func() is equivalent to Dummy.func(inst):

>>> out = Dummy.func(inst)
>>> out is inst
True

In its current form, there is no way for us to pass an argument to func when we are calling it from an instance of Dummy. To solve this issue, we will refactor our definition of func to anticipate the passing of the instance object as the first argument.

The self Argument

We will want to define our instance methods in a way that anticipates that Python will automatically pass an instance object as the first argument. Thus if we want our method to accept \(N\) external argument, we should define its signature to have \(N+1\) arguments, with the understanding that Python will pass the instance object as the first argument. The accepted convention is to call this first argument self. There is no significance to this name beyond it being the widely-adopted convention among Python users; “self” is meant to indicate that the instance object is passing itself as the first argument of the method. Consider the following example:

# demonstrate the use of `self` in instance arguments
class Number:
    def __init__(self, value):
        self.value = value

    def add(self, new_value):
        return self.value + new_value
# calls __init__, setting self.value = 4.0
>>> x = Number(4.0)

# `x` gets passed to `self`
>>> x.add(2.0)
6.0

# Calling the instance method from the class object.
# We must explicitly pass an object to `self`
>>> Number.add(x, 2.0)
6.0

Note the utility of having self be automatically passed in as an argument to both __init__ and add. An instance method is meant to have access to the instance object that is calling it - when you call capitalize from a string instance, it is obvious that you want to capitalize that specific string. It would be tedious and redundant if Python did not manage that automatically.

Next, we will see that we can also define class-methods, which automatically have class objects get passed as their first arguments, and static methods, which do not have any objects passed to them under the hood.

Reading Comprehension: Invoking Instance Methods

Rewrite Dummy so that its instance method func accepts two arguments: the instance object that Python automatically passes and the argument x, which we want func to return unchanged. Create an instance of Dummy and call func from this instance and pass it the string "hi", what will be returned? What will happen if you try to call Dummy.func("hi")? Why? How can we modify this call from Dummy itself so that the method will work as desired?

Class Methods

A class method is similar to an instance method, but it has a class object passed as its first argument. Recall that, when an instance method is called from an instance object, that instance object is automatically passed as the first argument to the method. By contrast, when a class method is called from a either a class object or an instance object, the class object is automatically passed as the first argument to the method. Instead of calling this first argument self, the convention is to name it cls.

To define a class method you must decorate the method definition with a special built-in decorator classmethod. We have not discussed decorators. Suffice it to know that this simply “tags” the method, so that Python knows to treat it like a class method instead of an instance method. The following demonstrates this decoration process:

class Dummy:

    @classmethod
    def class_func(cls):
        """ A class method defined to simply
            return `cls` unchanged"""
        return cls
# `Dummy` gets passed as `cls` automatically.
#  We defined `class_func` to return `cls` unchanged
>>> Dummy.class_func()
__main__.Dummy

# `Dummy.class_func()` returns `Dummy`
>>> out = Dummy.class_func()
>>> out is Dummy
True

# `Dummy` gets passed as `cls` automatically
# even when `class_func` is called from an instance
>>> inst = Dummy()
>>> inst.class_func()
__main__.Dummy

dict.fromkeys is an example of a class method that takes in an iterable, and returns a dictionary whose keys are the elements of that iterable, and whose values all default to None.

>>> dict.fromkeys("abcd", 2.3)
{'a': 2.3, 'b': 2.3, 'c': 2.3, 'd': 2.3}

It is sensible that this is a class method rather than an instance method, as the method creates a brand new dictionary from scratch. It need only have access to the dict object (i.e. the cls argument) so that it can construct the dictionary. The following is what an implementation of fromkeys could look like, were we to define dict ourselves:

class dict:
    # assume all other dictionary methods are defined here
    @classmethod
    def fromkeys(cls, iterable, value=None):
        """ Creates a dictionary whose keys are the elements of `iterable`. All
        keys map to `value`.

        Parameters
        ----------
        iterable: Iterable[Hashable]
            An iterable of valid dictionary keys (i.e. any object that is hashable).

        value : Optional[Any]
            The value that all of the keys will map to. Defaults to `None`.

        Returns
        -------
        dict """
        new_dict = cls()  # equivalent to `dict()`: creates a new dictionary instance
        for key in iterable:
            new_dict[key] = value
        return new_dict

Static Methods

A static method is simply a method whose arguments must all be passed explicitly by the user. That is, Python doesn’t pass anything to a static method automatically. The built-in decorator staticmethod is used to distinguish a method as being static rather than an instance method.

class Dummy:

    @staticmethod
    def static_func():
        """ A static method defined to always returns
            the string `'hi'`"""
        return 'hi'
# A static method can be called from a class object
# or an instance object; nothing gets passed to it
# automatically.
>>> Dummy.static_func()
'hi'

>>> inst = Dummy()
>>> inst.static_func()
'hi'

Reading Comprehension Solutions

Invoking Instance Methods: Solution

Rewrite Dummy so that its instance method func accepts two arguments: the instance object that Python automatically passes and the argument x, which we want func to return unchanged.

We will rewite func to accept an argument called ‘self’, which will accept the instance object that is passed “under the hood” , and ‘x’. As you will see in the reading, the name argument ‘self’ is simply used by convention.

class Dummy:
    def func(self, x):
        return x

Create an instance of Dummy and call func from this instance and pass it the string "hi".

>>> inst = Dummy()
>>> inst.func("hi")  # `inst` is passed to the argument `self`
'hi'

What will happen if you try to call Dummy.func("hi")? Why?

This will raise an error, which complains that func expects two arguments, and that we have only passed it one. Indeed, we will have only passed it the object “hi” and nothing else. Dummy is a class object, not an instance object. Thus Python does not do anything special “under the hood” when we call Dummy.func. We must pass something to the self argument. Because this particular method doesn’t do anything with self, we can just pass it None, or any other object, really.

# Dummy.func("hi") would raise an error
>>> Dummy.func(None, "hi")
'hi'