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'