Topics: class methods, instance methods, static methods, using class methods as alternative constructors
Updated 2020-10-03

Types of methods

A class can have three types of methods: instance methods, class methods and static methods.

Instance methods are the most common type of method. They take at least the parameter self as an input. This parameter points towards an instance of the class when the method is called. An instance method can modify both object state (through the self parameter) and class state indirectly (through the self.__class__ parameter).

Our base class already has an instance method. The says methods takes the input self and a string of words the current member of Castle Kilmere should say. When the says method is called on an object, the self parameter can be used to access attributes which were set in the dunder init method.

def says(self, words):
    return f"{self.name} says {words}"

Class methods look similar to instance methods in the sense that they take at least one parameter as an input. This parameter is, however, not self but cls. cls points towards the class - not a particular object instance - when the method is called. Therefore, a class method can only modify class state but not object state. Still, changing the class state will still affect all instances of the class. Another important thing to know about classmethods is that we need to use the @classmethod decorator when implementing a class method. We will talk about decorators in a later blog post. Right now you only need to remember to put a @classmethod on top of the function. An example for a class method can be seen below (see section on alternative constructors).

Static methods take neither a self nor cls parameter as an input. Therefore, they can modify neither object state nor class state. Although they are related to the class, they are yet independent and can only access the data they are provided with.

Do you remember the Pupil class? It had an attribute called _elms, standing for “Elementarey Level of Magic”. This attribute contains all the obligatory classes a pupil has to take. If a student passes one of her ELMs or not depends on the grade she gets. Castle Kilmere has a fixed grading scheme where grades range from ‘E’ for ‘Excellent’ to ‘H’ for ‘Horrible’. This is perfect material for a static method!

Our static method passed will receive a grade as an input and will return whether or not this means pass or fail. Notice that the method uses no class or instance state but only has access to the attributes its provided with (which is the grade in this case).

class Pupil(CastleKilmereMember):
    """ Create a Castle Kilmere Pupil """
    ...

    @staticmethod
    def passed(grade):
        """ Given a grade, determine if an exam was passed.  """
        grades = {
		'E': True,
		'Excellent': True,
                'O': True,
                'Ordinary': True,
                'A': True,
                'Acceptable': True,
                'P': False,
                'Poor': False,
                'H': False,
                'Horrible': False,
                }

        return grades[grade]

What are class and static methods good for?

Class and static methods allow a developer to communicate what intention she had in mind when implementing the method. For example, using a static method expresses that the method is independent from the rest of the class. Also, class methods can be used as alternative constructors.

Class methods as alternative constructors

  • In Python a class can only have one constructor (the self.__init__() method)
  • However, we can use @classmethod to create factory functions
  • These factory functions allow us to create class objects that are configured exactly the way we want them
  • In this way, they act like additional constructors

For example, we probably want to create the main characters of our Magical Universe. Typing all the names and other attributes every time would be very slow. Adding a few class methods allows us to speed up the process. We can create a Lissy constructor as follows:

@classmethod
def lissy(cls):
    return cls('Lissy Spinster',
                2008,
		'female',
		2020,
		('Ramses', 'cat'))

All code for today

class CastleKilmereMember:
    """ Creates a member of the Castle Kilmere School of Magic """
    def __init__(self, name, birthyear, sex):
        self.name = name
        self.birthyear = birthyear
        self.sex = sex
        
    def says(self, words):
        return f"{self.name} says: {words}"
    
    @classmethod
    def school_headmistress(cls):
        return cls('Miranda Mirren', 1962, 'female')   


class Pupil(CastleKilmereMember):
    """ Create a Castle Kilmere Pupil """
    def __init__(self, name, birthyear, sex, start_year, pet=None):
        super().__init__(name, birthyear, sex)
        self.start_year = start_year

        if pet is not None:
            self.pet_name, self.pet_type = pet

        self._elms = {
                  'Critical Thinking': False,
                  'Self-Defense Against Fresh Fruit': False,
                  'Broomstick Flying': False,
                  'Magical Theory': False,
                  'Foreign Magical Systems': False,
                  'Charms': False,
                  'Defence Against Dark Magic': False,
                  'History of Magic': False,
                  'Potions': False,
                  'Transfiguration': False}
    
    @classmethod
    def luke(cls):
        return cls('Luke Bery', 2008, 'male', 2020, ('Cotton', 'owl'))

    @classmethod
    def lissy(cls):
        return cls('Lissy Spinster', 2008, 'female', 2020, ('Ramses', 'cat'))

    @classmethod
    def adrien(cls):
        return cls('Adrien Fulford', 2008, 'male', 2020, ('Unnamed', 'owl') )



class Professor(CastleKilmereMember):
    """ Create a Castle Kilmere Professor """
    def __init__(self, name, birthyear, sex, subject, department):
        super().__init__(name, birthyear, sex)
        self.subject = subject
        self.department = department

    @classmethod
    def blade(cls):
        return cls('Blade Bardock', 1988, 'male', 'Potions', 'Science')

    @classmethod
    def briddle(cls):
        return cls('Birdie Briddle', 1931, 'female', 'Foreign Magical Systems', 'Law')

    @classmethod
    def radford(cls):
        return cls('Rupert Radford', 1958, 'male', 'Illusions 101', 'Creativity and Arts')

    @classmethod
    def giddings(cls):
        return cls('Gabriel Giddings', 1974, 'male', 'Broomstick Making', 'Engineering')

    
class Ghost(CastleKilmereMember):
    """ Creates a Castle Kilmere ghost """
    def __init__(self, name, birthyear, sex, year_of_death):
        super().__init__(name, birthyear, sex)
        self.year_of_death = year_of_death

if __name__ == "__main__":

    blade = Professor.blade()
    lissy = Pupil.lissy()