Definition
Picture a function whose job is to total up a shopping basket. It needs to know what is inside the basket, so the basket gets passed in as an argument. The trouble starts once several functions share that same basket: each one asks for the same information, and a single missed parameter introduces a bug that does not show up just by reading the code.
A method answers that annoyance by attaching the function to the object. It is a function defined inside a class and attached to its objects, written with the same def as an ordinary function: its first parameter receives the calling object and by convention it is named self.
Here is what that looks like once it sits inside a class.
class Basket:
def __init__(self):
self.items = []
def add(self, item):
self.items.append(item)
basket = Basket()
basket.add("keyboard")Look at the mismatch between the declaration and the call: add expects two parameters, self and item, yet only one is supplied. The dot in front of add does the missing work, telling Python which object should fill self. basket.add("keyboard") is exactly the same as Basket.add(basket, "keyboard"), nothing more.
What attaching the function to the object changes
Why bother with this detour instead of sticking to ordinary functions? The answer comes down to memory: a function receives its data on every call, a method lives with it permanently, in the form of the object attributes. It reads and changes every attribute of its object without an extra argument.
Compare the two versions below to see it for yourself.
# Without a method: every function asks for the same arguments
def add(items, item):
items.append(item)
def total(items, prices):
return sum(prices[i] for i in items)
# With a method: the data is already attached to the object
class Basket:
def add(self, item):
self.items.append(item)On a thirty-line script the first form is plenty. The difference is felt as soon as the same bundle of data travels between several functions, where a swapped parameter goes unnoticed until it breaks something. The method keeps everything about the basket in one place, which makes calling mistakes nearly impossible. It is a matter of organisation first, and only then a matter of technique.
The four families of methods
Not every method receives self the same way, and that is exactly what sorts them into four families, summed up in the table below.
| Family | First parameter | What it is for |
|---|---|---|
| Instance method | self, the object | Read or change one specific instance |
| classmethod | cls, the class | Build an object other than through __init__ |
| staticmethod | None | Keep a helper next to the class that uses it |
| magic method | self, the object | Give native behaviour: addition, display, length |
The first family covers most of what gets written day to day, and a beginner who knows nothing but the instance method already writes correct code. The next two are easy to spot, thanks to the at sign above the def. The last one is recognised by its two pairs of underscores, like __init__, and wires an object into Python native operators.
The trap of the method that returns nothing
A classic annoyance turns up when methods get chained as though they were ordinary functions. Many standard library methods change the object in place and hand back None, because the object has already been changed and there is nothing more to give back.
names = ["zoe", "adam", "lea"]
# Sorts the list in place and returns None: nothing to catch here
result = names.sort()
# Builds a brand new sorted list, leaving the original untouched
result = sorted(names)The bug that follows never fires on the names.sort() line itself. It waits a line further down, when an AttributeError reports that None has no such method.
Keep one simple rule in mind: whatever acts on the object in place returns None, whatever builds a result hands it back. sort() sorts the existing list without handing anything back, sorted() builds a new list and returns it.
Redefining an inherited method
What happens when a child class needs to behave almost like its parent, but not quite? It receives its parent methods through inheritance and may rewrite one under the same name.
class Customer:
def discount(self):
return 0
class LoyalCustomer(Customer):
def discount(self):
return super().discount() + 10Python always keeps the version closest to the object: if LoyalCustomer defines its own discount method, that is the one that runs, never the one from Customer. To avoid copying out what the parent class already does, super makes it possible to call its version from the child class and add only what is missing.
This is where a method really parts ways with an ordinary function. Its name no longer points at a fixed block of code, but at the block the object carries with it. Two different objects, called through the same line object.discount(), run two different bodies, and the calling code never needs to know which one fires.
Frequently asked questions
What is the difference between a method and a function?
A function stands alone and receives everything it needs as arguments. A method belongs to a class and additionally receives the object it is called on, which grants it access to that object state without an extra parameter.
Does self really have to be written in every definition?
Yes, for an instance method. Python supplies it automatically at call time, but it does not appear on its own in the signature: writing it in is up to you. Leaving it out produces an error about the number of arguments, always one more than expected, and it is often the very first mistake beginners run into.
How can the methods of an object be listed?
The dir(object) function gives the complete list, underscores included, and help(object) adds the explanations written by the author of the class. Our Python course covers classes once functions are firmly in place, because a method only makes sense at that point.