MicroPython Classes

Contents

Introduction

MicroPython is a “slim” version of Python specifically designed with a small footprint to efficiently run on memory constrained microcontrollers. MicroPython does not implement the complete complement of Python class functionality. This tutorial describes only those class features that are supported by MicroPython. All examples are original and have been tested on a micro:bit for correctness using the Mu Editor.

It is assumed that the reader has at least some familiarity with object-oriented programming concepts.

Definition

An excellent introduction to Python classes from favtutor.com:

“Classes are the focal point of OOP and objects are created by classes. The class can be defined as the description or the definition of the object. The class describes what the object will be but is totally separated from the object itself.

Also, a single class is used to describe multiple objects. For example, we create a blueprint before constructing the house. Similarly, the same blueprint can be used for different houses to be built. The programming works in the flow where first the class is defined which further describes the object. Each class has a name, and its own attribute and behavior.

The method is another term used in classes. Methods are the functions in the classes. They have a specific block of code that can be called and execute a certain task and return the values.”

Since MicroPython is an object-oriented language almost everything is an object consisting of methods and attributes. Methods are functions defined in a class and provide the 'doing', action or behaviour part of the class. Attributes are class variables that are inherited by every object of a class. Attributes and (closely related) properties are covered here.

In summary, a class is a blueprint or boilerplate for creating objects. A simple example is the best way to visualise this.

Example 1

# Simple demonstration of a class
# with two attributes.

class name_class():
    firstname = 'John' # attribute
    surname = 'Brown' # attribute

# Create object 'my_name'
# from 'name_class' blueprint
my_name = name_class()

# Create object 'her_name'
# from 'name_class' blueprint
her_name = name_class()
# Change the firstname and surname
# in this new object
her_name.firstname = 'Sue'
her_name.surname = 'Smith'

# Print out the names
print('My first name is', my_name.firstname)
print('My surname is', my_name.surname)
print() # prints blank line
print('Her first name is', her_name.firstname)
print('Her surname is', her_name.surname)

          
Output:

My first name is John
My surname is Brown

Her first name is Sue
Her surname is Smith
          

The class name_class() is defined with two attributes firstname and surname. The objects my_name and her_name are separate instances of this class.

Consider again the analogy of the house. Multiple houses can be built from a single set of house plans. While each house built from the same plans will have the same basic layout (rooms, staircases, etc) they can still be individualised e.g. each house will have its own paint scheme.

Two objects (my_name and her_name) are instantiated from the class name_class(). If the value of an attribute is then changed in one of the objects, the equivalent attribute of the other object is not altered i.e. they are truly different entities. Example 1 clearly shows this.

Object Methods

Methods are functions defined in classes and become part of an object created from the class. The Example 1 class name_class() is merely a simple data structure with two attributes. Example 2 adds functionality with the method full_name().

Example 2

# Simple demonstration of a class
# with two attributes and a method.

class name_class():
    firstname = 'John' # attribute
    surname = 'Brown' # attribute
    
    def full_name(self, title):
        return self.firstname + ' ' + self.surname

# Create object 'my_name'
# from 'name_class' blueprint
my_name = name_class()

# Create object 'her_name'
# from 'name_class' blueprint
her_name = name_class()
# Change the firstname and surname
# in this new object
her_name.firstname = 'Sue'
her_name.surname = 'Smith'

# Print out the names
print('My name is', my_name.full_name('Mr'))
print('Her name is', her_name.full_name('Ms'))
          

Output:


My name is Mr John Brown
Her name is Ms Sue Smith
          

There are several important things to note about methods. Methods are declared in exactly the same manner that any user function is defined with the def keyword. The difference, of course, is that the method is defined entirely within the class definition.

Note the parameter self[1] in the full_name(self, title) method. When the method code (contained in the class definition) is invoked, it must know which instantiated object has called it. MicroPython automatically populates this parameter with a reference to the calling object.

.

Magic Methods

Special methods known as magic methods in MicroPython are named with double leading and double trailing underscores. Examples include '__init__()' and __str__().

The magic methods have special meaning and programmers should avoid using variable names having double leading and double trailing underscores even though the Python language standard allows it.

The __init__() Method

All classes can have a method called __init__(), which if defined, is executed when an object from the class is being initiated. In OOP terms this is often referred to as a constructor.

The __init__() method assigns values to object attributes, and performs other operations that are necessary to do at the time the object is being created. It isn't mandatory to define this method in a class definition.

Example 3

# Demonstration of a class
# with a constructor.

class name_class():

    # Initialise attributes
    def __init__(self, firstname='John', surname='Brown'):
        self.firstname = firstname
        self.surname = surname   

    def full_name(self, title):
        return title + ' ' + self.firstname + ' ' + self.surname

# Create object 'my_name'
# from 'name_class' blueprint
my_name = name_class()

# Create object 'her_name'
# from 'name_class' blueprint
her_name = name_class('Sue', 'Smith')

# Print out the names
print('My name is', my_name.full_name('Mr'))
print('Her name is', her_name.full_name('Ms'))
          

Output:


My name is Mr John Brown
Her name is Ms Sue Smith
          

With the addition of the __init__() magic method the class name_class() becomes much more useful. The firstname and surname can now be passed as parameters when the object is being created.

Additionally, if these parameters are omitted then defaults can be provided e.g. 'John' and 'Brown' respectively in the case of Example 3.

In Example 3 adding the line:

print(my_name.first_name)

to the bottom of the program will return the error:

'name_class' object has no attribute 'first_name'.

The __str__() Method

Another useful builtin method that may be defined in the class definition is __str__(). If the object is used without an appended attribute or method e.g. print(her_name) then this method is invoked to return a string.

It is not mandatory to define this method in a class definition.

Example 4

# Demonstration of a class with
# magic methods __init__() and __str__()

class name_class():

    # Initialise attributes
    def __init__(self, firstname='John', surname='Brown'):
        self.firstname = firstname
        self.surname = surname

    def full_name(self, title = ''):
        if title != '': # title is provided
            return title + ' ' + self.firstname + ' ' + self.surname
        else: # no title provided
            return self.firstname + ' ' + self.surname
        
    def __str__(self):
        return self.full_name()

# Create object 'my_name'
# from 'name_class' blueprint
my_name = name_class()

# Create object 'her_name'
# from 'name_class' blueprint
her_name = name_class('Sue', 'Smith')

# Print out the names
print('My name and title is', my_name.full_name('Mr'))
print('My name is', my_name)
print('Her name is', her_name)
          
Output:

My name and title is Mr John Brown
My name is John Brown
Her name is Sue Smith
          

Different Types of Methods

There are three different types of methods:

  1. Instance Methods
  2. Class Methods
  3. Static Methods

Instance Methods

This is the basic no-frills method that will be used most of the time. The methods used in all of the above examples are instance methods.

They use the self parameter that provides a reference to the particular instance that is invoking the method. This ensures that the parameter values specific to that object are being used.

Class Methods

Instead of accepting a self parameter, class methods take a cls[2] parameter that points to the class — and not the object instance - when the method is called.

Class methods don't have access to the object instance of the class since it only has the cls parameter and not the self parameter.

Class methods are indicated by placing the Python decorator[3] @classmethod just before the method definition in the class. See Example 5.

Example 5

# Demonstration of a class type method

class Error:
    # dictionary of error messages
    err_dict = {1 : '<Too many apples>',
                2 : ''<The sky is too blue>',
                3 : ''<The lake is full>',
                4 : ''<Everyone is drunk>',
                5 : ''<The battery is flat>'}
    
    # Constructor
    def __init__(self, name = 'generic'):
        self.name = '<' + name + '>'
        self.err_dict = {1 : 'One',
                         2 : 'Two',
                         3 : 'Three',
                         4 : 'Four'}
        
    def object_name(self):
        return self.name
    
    # Return error message
    @classmethod
    def msg(cls, err_num):
        if err_num '< 1 or err_num > len(cls.err_dict):
            return '<Invalid error number>'
        else:
            return cls.err_dict[err_num]

# Instantiate some objects from class Error()
my_err = Error('my_err')
her_err = Error('her_err')

# Get an error message
my_err4 = my_err.msg(4)
her_err4 = her_err.msg(4)

# Print error message
print('object', my_err.object_name(), 'says error 4 is', my_err4)
print('object', her_err.object_name(), 'says error 4 is', her_err4)

            
Output:
object <my_err> says error 4 is <Everyone is drunk>
object <her_err> says error 4 is <Everyone is drunk>
            

Since the method msg(cls, err_num) is a class method it will look up the class version of err_dict to match the error number with the error message. If it instead was an instance method it would look up the calling object's version of err_dict.

The classmethod() function will also convert a function to a class method. This function is considered old fashioned and the preferred way is the use of the decorator. For those interested classmethod(), is covered here.

Some OOP languages such as C++ allow declarations of multiple constructors. Usually these constructors will have differing types and/or number of parameters. This provides flexibility when an object is created from a class.

Python doesn't directly allow multiple __init__() declarations but this limitation is easily gotten around by the use of class methods as factory functions.

Example 6 provides a simple example of a class from which class methods are used as factory functions, allowing flexibility in the way an object is instantiated from the class.

Static Methods

A static method takes neither a self nor a cls parameter (but of course it is free to accept an arbitrary number of other parameters). It cannot alter the state of a class or an object instantiated from a class. In essence it is a standalone function that happens to be included in a class definition.

Similar to class methods, the accepted way of designating a method as static is to use a Python decorator placed in the line above the method code. In this case the decorator is @staticmethod.

Static methods can provide useful functionality within a class by providing support for instance and class methods.

The birthday() class from Example 6 includes a static method which accepts an integer representing a month and returns a three character string of the month's name. So if 10 is passed to this function, it will return 'Oct'.

Example 6

# A demonstration of all three method types.

# There is a constructor which accepts a
# birthday date in a very specific format.
# Four class methods provide a more flexible
# style of constructor whereby the user
# is able to choose whatever date format
# they wish.

# The static method is there only as a 
# simple support function.

class birthday:
    def __init__(self, day, month, year):
        self.day = day
        self.month = month
        self.year = year

    @classmethod
    def DMY(cls, day, month, year):
        return cls(day, month, year)

    @classmethod
    def MDY(cls, month, day, year):
        return cls(day, month, year)

    @classmethod
    def YMD(cls, year, month, day):
        return cls(day, month, year)

    @classmethod
    def YDM(cls, year, day, month):
        return cls(day, month, year)

    def fbirthday(self):
        # An instance method that returns
        # a formatted string of the birthday date.
        return str(self.day) + "/" + birthday.mtext(self.month) + "/" + str(self.year)

    @staticmethod
    def mtext(month):
        return ['jan', 'Feb', 'Mar',
                'Apr', 'May', 'Jun',
                'Jul', 'Aug', 'Sep',
                'Oct', 'Nov', 'Dec'][month - 1]


sue_birthday = birthday.DMY(2, 10, 2001)
print("Sue's birthday is", sue_birthday.fbirthday())

bill_birthday = birthday.YDM(1965, 30, 12)
print("Bill's birthday is", bill_birthday.fbirthday())

penny_birthday = birthday.MDY(2, 22, 1963)
print("Penny's birthday is", penny_birthday.fbirthday())

            
Output:

Sue's birthday is 2/Oct/2001
Bill's birthday is 30/Dec/1965
Penny's birthday is 22/Feb/1963
            

The staticmethod() function will also convert a function to a static method. This function is considered old fashioned and the preferred way is the use of the decorator. For those interested classmethod(), is covered here.

Wrap Up

This article has discussed the syntax for defining MicroPython classes. Methods and the different types as defined by the Python language (and implemented by MicroPython) have been examined in some detail with examples.

Most programmers using MicroPython to write simple programs for the control of microcontrollers in real life scenarios will get by quite adequately with a good basic understanding of classes and their components (attributes and methods).

Those developers wishing to build more complex projects such as generic multi-user libraries e.g. to provide a supporting API module for a sensor series, may wish to pursue the more advanced OOP features supported by MicroPython. This series covers class inheritance and polymorphism here.