Python — OOP Part 4: Class Method and Static Method
In this lesson we’ll cover class methods and static methods. Both are defined inside a class. A class method is for creating, modifying, or referencing class variables. A static method is a utility method — a class-related function defined inside the class so you can call it conveniently through the class or an instance.
The instance method we covered previously takes self (the instance) as its argument and creates, modifies, or references data scoped to a single instance, like an instance variable. A class method takes cls (the class) as its argument and creates, modifies, or references data shared across all instances, like a class variable. Let’s go through how to use class methods with an example.
class Employee:
raise_amount = 1.1 # 1 class variable for the salary raise rate
def __init__(self, first, last, pay):
self.first = first
self.last = last
self.pay = pay
self.email = first.lower() + '.' + last.lower() + '@schoolofweb.net'
def apply_raise(self):
self.pay = int(self.pay * self.raise_amount)
def full_name(self):
return '{} {}'.format(self.first, self.last)
def get_pay(self):
return 'The current salary of "{}" is "{}".'.format(self.full_name(), self.pay)
emp_1 = Employee('Sanghee', 'Lee', 50000)
emp_2 = Employee('Minjung', 'Kim', 60000)
# before raise
print(emp_1.get_pay())
print(emp_2.get_pay())
# apply raise
emp_1.apply_raise()
emp_2.apply_raise()
# after raise
print(emp_1.get_pay())
print(emp_2.get_pay())The current salary of "Sanghee Lee" is "50000".
The current salary of "Minjung Kim" is "60000".
The current salary of "Sanghee Lee" is "55000".
The current salary of "Minjung Kim" is "66000".In the code above, we defined the class variable raise_amount and used the instance method apply_raise so that the same rate gets applied to every employee instance when raising pay. If next year you need to change the raise rate, the best way is to use a class method. You could also update the class variable directly with something like Employee.raise_amount = 1.2, but a class method is handy when you need data validation or other extra logic alongside the update. Look at the code below.
class Employee:
raise_amount = 1.1 # class variable for the salary raise rate
def __init__(self, first, last, pay):
self.first = first
self.last = last
self.pay = pay
def apply_raise(self):
self.pay = int(self.pay * self.raise_amount)
def full_name(self):
return '{} {}'.format(self.first, self.last)
def get_pay(self):
return 'The current salary of "{}" is "{}".'.format(self.full_name(), self.pay)
# 1 define a class method using the @classmethod decorator
@classmethod
def change_raise_amount(cls, amount):
# 2 if the rate is less than "1", ask for re-input
while amount < 1:
print('[Warning] The raise rate cannot be less than "1".')
amount = input('[Input] Please enter the raise rate again.\n=> ')
amount = float(amount)
cls.raise_amount = amount
print('Raise rate "{}" has been applied.'.format(amount))
emp_1 = Employee('Sanghee', 'Lee', 50000)
emp_2 = Employee('Minjung', 'Kim', 60000)
# before raise
print(emp_1.get_pay())
print(emp_2.get_pay())
# change the raise rate
Employee.change_raise_amount(0.9)
# apply raise
emp_1.apply_raise()
emp_2.apply_raise()
# after raise
print(emp_1.get_pay())
print(emp_2.get_pay())The current salary of "Sanghee Lee" is "50000".
The current salary of "Minjung Kim" is "60000".
[Warning] The raise rate cannot be less than "1".
[Input] Please enter the raise rate again.
=> 1.2
Raise rate "1.2" has been applied.
The current salary of "Sanghee Lee" is "60000".
The current salary of "Minjung Kim" is "72000".At #1 we defined a class method using the class method decorator. At #2 we ran a data integrity check and only updated the class variable when the value was greater than 1. Class methods are sometimes also used as alternative constructors. Let’s see an example.
class Person:
def __init__(self, year, month, day, sex):
self.year = year
self.month = month
self.day = day
self.sex = sex
def __str__(self):
return 'Born {}-{}-{}, {}.'.format(self.year, self.month, self.day, self.sex)
person_1 = Person(1990, 8, 29, 'male')
print(person_1)Born 1990-8-29, male.We used a class with date-of-birth and sex data. But suppose you can’t always pass year, month, day, and sex as separate arguments — sometimes you have to create the instance from a Korean Resident Registration Number (RRN) instead. You could write code like this:
class Person:
def __init__(self, year, month, day, sex):
self.year = year
self.month = month
self.day = day
self.sex = sex
def __str__(self):
return 'Born {}-{}-{}, {}.'.format(self.year, self.month, self.day, self.sex)
ssn_1 = '900829-1034356'
ssn_2 = '051224-4061569'
def ssn_parser(ssn):
front, back = ssn.split('-')
sex = back[0]
if sex == '1' or sex == '2':
year = '19' + front[:2]
else:
year = '20' + front[:2]
if (int(sex) % 2) == 0:
sex = 'female'
else:
sex = 'male'
month = front[2:4]
day = front[4:6]
return year, month, day, sex
person_1 = Person(*ssn_parser(ssn_1))
print(person_1)
person_2 = Person(*ssn_parser(ssn_2))
print(person_2)Born 1990-08-29, male.
Born 2005-12-24, female.Using a standalone function like ssn_parser to parse the RRN and then unpacking the tuple into the class constructor works, but a better approach is to make an alternative constructor as a class method. Look at the example.
class Person:
def __init__(self, year, month, day, sex):
self.year = year
self.month = month
self.day = day
self.sex = sex
def __str__(self):
return 'Born {}-{}-{}, {}.'.format(self.year, self.month, self.day, self.sex)
@classmethod
def ssn_constructor(cls, ssn):
front, back = ssn.split('-')
sex = back[0]
if sex == '1' or sex == '2':
year = '19' + front[:2]
else:
year = '20' + front[:2]
if (int(sex) % 2) == 0:
sex = 'female'
else:
sex = 'male'
month = front[2:4]
day = front[4:6]
return cls(year, month, day, sex)
ssn_1 = '900829-1034356'
ssn_2 = '051224-4061569'
person_1 = Person.ssn_constructor(ssn_1)
print(person_1)
person_2 = Person.ssn_constructor(ssn_2)
print(person_2)Born 1990-08-29, male.
Born 2005-12-24, female.The result is the same as before, but defining the parser inside the class as a method makes the code more elegant.
Now let’s look at how to use static methods. A lot of people get class methods and static methods confused, so in this lesson let’s nail down the concepts of all three: instance methods, class methods, and static methods.
All three are defined inside a class. Instance methods are called through an instance and automatically receive the instance itself as the first argument; by convention this argument is called self. Class methods are called through the class, are defined with the @classmethod decorator, and the class itself is automatically passed as the first argument; by convention this argument is called cls. Static methods — unlike the other two — do not receive the instance or the class as the first argument. A static method is defined inside a class and lives in the class namespace, but otherwise it’s no different from a regular function. Still, when a function is conceptually related to the class, defining it inside the class lets you call it through the class or an instance, which is more convenient. Look at the example below.
import datetime
class Person:
my_class_var = 'sanghee'
def __init__(self, year, month, day, sex):
self.year = year
self.month = month
self.day = day
self.sex = sex
def __str__(self):
return 'Born {}-{}-{}, {}.'.format(self.year, self.month, self.day, self.sex)
@classmethod
def ssn_constructor(cls, ssn):
front, back = ssn.split('-')
sex = back[0]
if sex == '1' or sex == '2':
year = '19' + front[:2]
else:
year = '20' + front[:2]
if (int(sex) % 2) == 0:
sex = 'female'
else:
sex = 'male'
month = front[2:4]
day = front[4:6]
return cls(year, month, day, sex)
@staticmethod
def is_work_day(day):
# weekday() returns:
# Mon: 0, Tue: 1, Wed: 2, Thu: 3, Fri: 4, Sat: 5, Sun: 6
if day.weekday() == 5 or day.weekday() == 6:
return False
return True
ssn_1 = '900829-1034356'
ssn_2 = '051224-4061569'
person_1 = Person.ssn_constructor(ssn_1)
print(person_1)
person_2 = Person.ssn_constructor(ssn_2)
print(person_2)
# create a Sunday date object
my_date = datetime.date(2016, 10, 9)
# call the static method through the class
print(Person.is_work_day(my_date))
# call the static method through an instance
print(person_1.is_work_day(my_date))Born 1990-08-29, male.
Born 2005-12-24, female.
False
FalseUsing the @staticmethod decorator we created a static method called is_work_day that returns whether a given date is a workday.
The next post covers class inheritance and subclasses.