MicroPython 'ucollections' Module

Contents

Introduction

MicroPython is a 'slim' version of the ever popular Python programming language. With its much smaller footprint it is an effective and easily learnt language for programming microcontrollers.

The BBC micro:bit was originally designed and mass produced as an educational device. However many enthusiasts have discovered this interesting little device and it is increasingly becoming explored by microcontroller hobbyists.

The version of MicroPython ported to the micro:bit is an even smaller subset of Python but is perfectly adequate to squeeze interesting functionality out of this device.

Purpose

The MicroPython ucollections module for the micro:bit implements specialised collection datatypes providing alternatives to MicroPython's general purpose built-in collection objects; tuple and dict.

The much richer Python version is the collections module. The MicroPython module may also be referred to by the same name. However for consistency this should be resisted. The 'u' prefix in ucollections represents the micro from microcontrollers.

The ucollections.namedtuple and ucollections.OrderedDict datatypes provide extended functionality over their base class objects. Their usage promotes code that is more readable and hence easier to maintain and extend.

An import statement is necessary before using these datatypes. This can be one of any of the following forms:


import ucollections
from ucollections import namedtuple
from ucollections import OrderedDict
          

The namedtuple Subclass

The namedtuple is a tuple subclass with a specific name and named fields. They are defned with the namedtuple() function.

Syntax


Syntax:
ucollection.namedtuple(name, fields)

Where:
  name is the name for the new structure,
  and is used for declaring instances of
  the defined namedtuple.

  fields is a list of strings specifying
  field names.

Example:
from ucollections import namedtuple

# Defining the namedtuple
Date = namedtuple('Date', ['day', 'month', 'year'])

# Creating an instance of the namedtuple
myDate = Date(28, 'Feb', 2024)
print(myDate)

⇒ <Date(day=28, month='Feb', year=2024)>

The namedtuple Date from the above example could be described in pseudocode as:


Record: Date
  day: 1..31
  month: 'Jan'..'Dec'
  year: unsigned int
End Date

            

Accessing namedtuple Fields

There are three convenient ways to access the field values of a namedtuple:

  1. Index
  2. Field name
  3. Function getattr()

Access Operations
The Date namedtuple will be used in all examples:

from ucollections import namedtuple
Date = namedtuple('Date', ['day', 'month', 'year'])
myDate = Date(28, 'Feb', 2024)

Access by index:
print(myDate[0], myDate[1], myDate[2])
⇒ 28 Feb 2024

Access by field name:
print(myDate.day, myDate.month, myDate.year)
⇒ 28 Feb 2024

Access by getattr()> function:
Day = getattr(myDate, 'day')
Month = getattr(myDate, 'month')
Year = getattr(myDate, 'year')
print(Day, Month, Year)
⇒ 28 Feb 2024
            

Since namedtuple is a subclass of tuple it follows that they are immutable i.e. field values can't be changed. Attempting to change the value of a field results in an AttributeError exception.

Example 1

from ucollections import namedtuple
point3D = namedtuple('point', ['x', 'y', 'z'])
myPoint = point3D(5, 2.3, 9)
print('MyPoint is:', myPoint)
print()

# Attempting to change a value will
# result in an error.
myPoint.y = 5
            
Output:

MyPoint is: point(x=5, y=2.3, z=9)

Traceback (most recent call last):
  File "<stdin>", line 7, in <module>
AttributeError: can't set attribute
            

Multiple instances of a namedtuple can be stored in a list for further processing. The next example populates two namedtuple instances and stores them in a list. The list is iterated over to access and print the value of a given field.

Example 2

from ucollections import namedtuple

# Define a list of coordinates.
L = [9, 1, 7, 5.6]

# Define a point namedtuple.
# Create point instances from the list
# of coordinates.
point = namedtuple('point', ['x', 'y'])
Point1 = point(L[0], L[1])
Point2 = point(L[2], L[3])

# Store all the point instances
# in a list.
PointList = [Point1, Point2]
print(PointList)

# Access the 'x' field of each point.
print('\nx values of all points:')
for i in PointList:
    print(i.x)
print()
            
Output:

[point(x=9, y=1), point(x=7, y=5.6)]

x values of all points:
9
7
            

Converting a Dictionary

A dictionary can be converted to a namedtuple using the ** unpacking operator.

Example 3

# Convert a dictionary into a namedtuple
# using the dictionary ** unpacking operator.

from ucollections import namedtuple

# Define a dictionary
Dictionary = {'x':8, 'y':9.1, 'z':5}
print(Dictionary)

# Define a namedtuple
point3D = namedtuple('point3D', ['x', 'y', 'z'])

# Create an instance of point3D
# populated from the dictionary.
myPoint3D = point3D(**Dictionary)
print(myPoint3D)
            
Output:

{'x': 8, 'z': 5, 'y': 9.1}
point3D(x=8, y=9.1, z=5)
            

The output from the above example shows that while the dictionary type is unordered (accessed by key), a namedtuple by virtue of being a tuple subclass is ordered.

The OrderedDict Subclass

A OrderedDict is a dictionary subclass that remembers the order in which keys are added. When the OrderedDict is iterated the keys and associated values are returned in original order of insertion. This is the only distinction between an OrderedDict and a dictionary.

An OrderedDict is declared with the OrderedDict() function. Its use is intuitive for those familiar with MicroPython dictionaries.


from ucollections import OrderedDict

# Declare an OrderedDict that is empty.
odict = OrderedDict()
print(odict)
⇒ OrderedDict({})

# Declare an OrderedDict with some keys
odict = OrderedDict({'A':1, 'B':2})
print(odict)
⇒ OrderedDict({'A': 1, 'B': 2})
          

Note in the example above how the order that keys were added has been preserved.

Example 4

# Demonstrates that an OrderedDict remembers
# the order that keys are inserted.

from ucollections import OrderedDict

# Declare an OrderedDict with two keys.
ODict = OrderedDict({'A':1, 'B':2})
# Declared a dictionary with the
# same two keys.
Dict = {'A':1, 'B':2}

# Add another key to both.
ODict.update({'C':3})
Dict.update({'C':3})

# Now, check the order of the keys.
print(ODict)
print(Dict)
            
Output:

OrderedDict({'A': 1, 'B': 2, 'C': 3})
{'A': 1, 'C': 3, 'B': 2}
          

As Example 4 shows the order of the keys in the OrderedDict is maintained while the key order of the dictionary is not guaranteed.