Classes can be created as simply a collection of functions. The functions can be defined within the class exactly the same way that functions are normally created. In order to call these functions we need to call it from the class. So the example class called Greetings below takes a name and returns a greeting to the person calling the class.
class Greetings:
def good_morning(name):
print(f'Good morning {name}')
def good_afternoon(name):
print(f'Good afternoon {name}')
def good_evening(name):
print(f'Good evening {name}')
Greetings.good_afternoon('John')
Greetings.good_morning('Peter')
Greetings.good_evening('Jane')
Out:
Good afternoon John
Good morning Peter
Good evening Jane
Common Mistake
Although the class above behaves as we would expect. Let's take the exact same class and add an __init__. See this article if you don't know what the __init__ is.
class Greetings:
def __init__(self):
pass
def good_morning(name):
print(f'Good morning {name}')
def good_afternoon(name):
print(f'Good afternoon {name}')
def good_evening(name):
print(f'Good evening {name}')
g = Greetings()
We have created an instance of the Greetings class named g. If we try to call the functions we defined within the class as follows:
g.good_afternoon('John')
TypeError: good_afternoon() takes 1 positional argument but 2 were given
It may not immediately be clear as to why this is happening. The TypeError returned above indicates that we have passed 2 arguments to the function, when it appears we have only passed one. Let's try calling it without a name to see what happens:
g.good_afternoon()
# out:
Good afternoon <__main__.Greetings object at 0x00000284083D1B88>
What is going on here?
When we create an instance of a class the first argument passed to the function is the instance itself. So the reason we are getting the TypeError is that the way Python is reading the g.good_afternoon('John') function is g.good_afternoon(g, 'John') although this may seem confusing, it will become clear in the next section why this happens.
Instance Methods
Taking a new example class called Student, which takes a first name, last name, age and major.
class Student:
def __init__(self, first, last, age, major):
self.first = first
self.last = last
self.age = age
self.major = major
def profile(self):
print(f"Student name {self.first + ' ' + self.last}")
print(f"Student age: {self.age}")
print(f"Major: {self.major}")
s = Student('Sally' , 'Harris', 20, 'Biology')
s.profile()
Out:
Student name Sally Harris
Student age: 20
Major: Biology
When creating instance methods we must pass the self keyword, this takes care of the issue of the instance being passed to functions as the first argument. Let's build on the current class and add enrollment functionality to show the ability of instance methods to interact with attributes.
class Student:
def __init__(self, first, last, age, major):
self.first = first
self.last = last
self.age = age
self.major = major
self.courses = []
def profile(self):
print(f"Student name {self.first + ' ' + self.last}")
print(f"Student age: {self.age}")
print(f"Major: {self.major}")
def enrol(self, course):
self.courses.append(course)
print(f"enrolled {self.first} in {course}")
def show_courses(self):
print(f"{self.first + '' + self.last} is taking the following courses")
for course in self.courses:
print(course)
s = Student('Sally' , 'Harris', 20, 'Biology')
s.enrol('Biochemistry I')
# enrolled Sally in Biochemistry I
s.enrol('Literature')
# enrolled Sally in Literature
s.enrol('Mathematics')
# enrolled Sally in Mathematics
s.show_courses()
# SallyHarris is taking the following courses
# Biochemistry I
# Literature
# Mathematics
All of the methods above have been bound to the instance (we named this instance s), we can check this by using the dir keyword on the instance to see all the attributes and methods bound to s.
dir(s)
Out:
['__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__',
'age',
'courses',
'enrol',
'first',
'last',
'major',
'profile',
'show_courses']
We can add a mix of instance bound functions and normal functions as defined at the beginning of the document. Below we add a method that prints the current academic year.
import datetime as dt
class Student:
def __init__(self, first, last, age, major):
self.first = first
self.last = last
self.age = age
self.major = major
self.courses = []
def profile(self):
print(f"Student name {self.first + ' ' + self.last}")
print(f"Student age: {self.age}")
print(f"Major: {self.major}")
def enrol(self, course):
self.courses.append(course)
print(f"enrolled {self.first} in {course}")
def show_courses(self):
print(f"{self.first + '' + self.last} is taking the following courses")
for course in self.courses:
print(course)
def academic_year():
now = dt.datetime.now()
s = now.year, now.year -1
print(f"Current academic year is { str(s[0]) + '/' + str(s[1]) }")
However, we will still get an error if we try to call this new function from an instance, as we have established that calling methods/ functions from an instance always passes the instance itself in as the first argument. So If we wanted to call this academic year function we can do so as follows:
Student.academic_year()
# returns: Current academic year is 2020/2019
A better way to handle this will be the topic of the next article on class & static methods.
Summary
- When calling a function from a class the syntax is ClassName.FunctionName()
- When calling a function bound to an instance of a class, the first argument passed in to the function is the instance itself. These are known as methods
- To access and call a method correctly we should pass self in as the first argument when writing the code for the method.
- These methods allow us to interact with attributes associated with the instance / class.