MicroPython - User Defined Functions Part 1

Contents

Introduction

MicroPython is a “slim” version of Python specifically designed with a small footprint to efficiently run on memory constrained microcontrollers.

MicroPython doesn't provide anywhere near the complete complement of Python functionality. However the subset that is implemented is useful, fit-for-purpose and mostly all that is required.

All examples in this article are original and have been tested on a micro:bit for correctness using the Mu Editor.

This article is a beginners treatise into the world of MicroPython's user defined function. Other MicroPython articles in this series that the reader might find relevant to the topic covered in this posting include:

What is a Function?

The definition of a computer coded function at realpython.com explains it in succinct and simple terms.

In programming, a function is a self-contained block of code that encapsulates a specific task or related group of tasks.

Functions may be built-in as part of the language or standard library modules. MicroPython (as do many other computer languages) also provides the programmer with the ability to code user defined functions.

Examples of common MicroPython built-in or standard library functions that coders should be familiar with include:


Built-in functions
len('Spam') ⇒ 4
min([34, 12, 78]) ⇒ 12
pow(2, 4) ⇒ 24 = 16

From the math standard library
math.sqrt(5) ⇒ 2.236068

These built-in functions perform a single task. The programmer only needs to understand the function's syntax, including what arguments (if any) are required and what values (if any) are returned. Knowledge of the function's code is unnecessary and indeed; undesirable.

In a similar manner the programmer can define their own functions with a published interface describing only what the function does and how to use it. Such functions may be bundled up into modules and made available to others.

This promotes code reuse; write once - use many times; always a very good thing.

Functions allow a program to be broken down into more manageable chunks that aid design, readability and debugging. Think of it as the Romans did; divide and conquer!

Writing a User Defined Function

The keyword def is the function header and is used to signify that the block of code following is a user defined function.

Syntax

    def function_name([parameters]):
        statement(s)

Parameters:
    function_name: An identifier that names the function.

    parameters: An optional list of values passed 
                to the function when it is invoked.

    statement(s): A block of one or more MicroPython
                  statements. This block must be indented
                  from the function header.

          

The block of code following the header must be indented - see Example 1. Code indentation is a very important part of the MicroPython language. Any code block following a statement that ends with a colon : (eg function and class headers, decision and looping statements) must be indented.


User defined function
my_fnct(par1, par2):
  statement_1
  statement_2
      ...
  statement_n

Main program
program_statement_1
program_statement_2
      ...
d1 = my_fnct(p1, p2)
      ...

          

A function's code is not executed till invoked (called) from elsewhere within the program.

Example 1

# Demonstrates a user defined function.

def Repeat(item, num):
    # Writes value of 'item' out 'num' times.
    # 'item' can be almost any type.
    # 'num' must be an integer.
    print('\n--------------------------')

    # Check that 'num' is an integer
    if isinstance(num, int):
        for count in range(1, num+1):
            print(item)
    else:
        print('Parameter Error: integer expected')
    
    print('--------------------------')

Repeat('Spam', 3)
Repeat({3, 4, 5, 6}, 2)
Repeat('Bad', 5.3)

          
Output:

--------------------------
Spam
Spam
Spam
--------------------------

--------------------------
{4, 5, 6, 3}
{4, 5, 6, 3}
--------------------------

--------------------------
Parameter Error: integer expected
--------------------------

          

The user defined function Repeat(item, num) takes two parameters. The function writes out the parameter item a number of times given by the parameter num. For example Repeat('It is a hairy dog', 3) will produce the following output:


--------------------------
It is a hairy dog
It is a hairy dog
It is a hairy dog
--------------------------

          

While num must be an integer (and the function enforces this), item can be of almost any data type. Example 1 demonstrates this powerful feature of MicroPython with three separate calls to Repeat(), with each item being a different data type each time.

Returning Values from a Function

The return statement serves two purposes:

  1. Immediately causes execution of the function to cease with control passed back to the caller.
  2. Provides a mechanism for passing back values to the caller.

These two points are best illustrated by example.copy and flash the following code onto the micro:bit.

Example 2

# Demonstrates the use of the 'return' statement
# in a user defined function.

# This program generates random integers between
# 'LowerLimit' and 'UpperLimit' till one is found
# that is larger than 'BigNumber'.

import random # Generates random numbers
import utime # Provides timing functions

# Initialise random number generator.
random.seed(utime.ticks_us())

def isBig(num, big):
    if num <= big:
        return False
    else:
        return True
        
LowerLimit = 1
UpperLimit = 200
BigNumber = 192

count = 1
rndnum = random.randint(LowerLimit, UpperLimit)
while not isBig(rndnum, BigNumber):
    count += 1
    rndnum = random.randint(LowerLimit, UpperLimit)
    
print('The number', rndnum, 
      '(>', BigNumber, ')'
      ' was found after', count, 'attempts.')

          
Typical Output:

The number 197 (> 192 ) was found after 18 attempts.

          

A random integer between two given limits, ( LowerLimit – UpperLimit), is generated and passed to the function isBig(num). If num <= BigNumber then the return statement causes the function to immediately pass execution back to the main program, which will generate another random integer and repeat the function call.

However if the random number is greater than BigNumber the return statement passes back the value of True as well as execution to the caller. The while loop in the main program will now cease and the number of attempts is output..

Returning Multiple Values from a Function

Both function examples above either return no values or at most a single value. However it is possible for functions to return multiple values. The use of this feature often results in neat and compact code.


def power(value):
    return pow(value, 2), pow(value, 3), pow(value, 4)
    
a, b, c = power(14.92)
print(a, ',', b, ',', c)

  ⇒ 222.6064 , 3321.288 , 49553.61

            

In this simple example the function power() takes a single numeric argument value and returns three numerics being value2, value3 and value4.

Example 3 is more meaningful. Here, the side measurement of a cube is passed to the function cube(). This function calculates and returns the diagonal length, surface area and volume of the cube.

Example 3

# Demonstrates a function
# that returns multiple values.

def isNumeric(value):
    # Checks whether 'value' is numeric or not.
    if isinstance(value, (int, float)):
        return True
    else:
        return False

# Returns diagonal measurement, surface area
# and volume of a cube given the cube
# side measurement. 
def cube(side):
    # Takes side measurement of a cube.
    # By definition all sides of a cube
    # are the same size.
    
    # None value is returned
    # if 'side' is nonnumeric.
    diagonal = surface = volume = None
    if isNumeric(side):
        diagonal = side * pow(3, 1/3)
        surface = 6 * side * side
        volume = side * side * side
    return diagonal, surface, volume
 
# Define a cube
side = 5.93 
print('My cube has side length of', side)

# Calculate cube parameters
d, s, v = cube(side)
print('Diagonal length =', d)
print('Surface area =', s)
print('Volume =', v)

            
Output:

My cube has side length of 5.93
Diagonal length = 8.55254
Surface area = 210.9894
Volume = 208.5278
            

Function Argument Passing

Consider the function defined in Example 1

Repeat(item, num)

The variables in brackets, item and num are referred to as parameters.

The function can be called for example:

Repeat('It is a hairy dog', 3)

Here, the string 'It is a hairy dog' and the integer 3 are called arguments. When the function is executed, these two arguments are passed as values to the corresponding parameters in the function.

It is important to note that arguments passed to a MicroPython function are always copies of the originals. This means that if the MicroPython function code changes a parameter's value this is only changing the value of the local copy within the body of the function.

Unlike MicroPython, other languages do provide direct mechanisms for passing parameters by reference as well as by copy to functions. MicroPython provides the memoryview object[1] as an indirect means for passing a very limited range of parameter types to functions by reference.

Positional Arguments

Most functions will take arguments. There are several different mechanisms for passing arguments to a function. The simplest and most common approach is with positional arguments (also called required arguments). Here, each argument in the function call is matched in order with the function parameters.

Example 1 above uses positional arguments. Consider another simpler example:

Example 4 - Positional Arguments

# Demonstrates the use of keyword arguments
# in calling a user defined function.

def divide(num1, num2):
    # This function divides two numbers.
    # No error checking is performed.
    return num1 / num2

answer = divide(2, 5)
print('2 divided by 5 =', answer)

            
Output:

2 divided by 5 = 0.4

            

On calling the function divide(num1, num2) with ‘divide(2, 5)’ the arguments are passed by position:

2  num1
5  num2
            

Keyword Arguments

When calling a function, the arguments can be specifically called in the form keyword = value. In that case, each keyword must match a parameter in the Python function definition.

Example 5 reworks Example 4 to demonstrate the concept and produces the same output.

Example 5 - Keyword Arguments


# Demonstrates the use of keyword arguments
# in calling a user defined function.

def divide(num1, num2):
    # This function divides two numbers.
    # No error checking is performed.
    return num1 / num2

answer = divide(num1=2, num2=5)
print('2 divided by 5 =', answer)

            
Output:

2 divided by 5 = 0.4

            

With the use of keyword arguments it doesn't matter which order the arguments are passed to the function. For example, using Example 5, the calls divide(num1 = 2, num2 = 5) and divide(num2 = 5, num1 = 2) are equivalent and will return the same result.

Positional and keyword arguments can be combined in a function call but the correct order of positional arguments must be maintained. For example, using Example 4, the calls divide(num2 = 5, num1 = 2) and divide(2, num2 = 5) are equivalent and will return the same result.

When positional and keyword arguments are both present, all the positional arguments must come first. For example divide(num1 =2, 5) will cause the exception non-keyword arg after keyword arg to be raised.

Default Arguments

If a parameter in a Python function definition is also assigned a value in the form name = value, then value becomes a default value for that parameter. Parameters defined this way are referred to as default or optional parameters.

Example 6 - Default Arguments

# Demonstrates the use of default arguments
# in calling a user defined function.

def binomial(x, a=1, b=1, c=1):
    # Calculates the value of a binomial equation.
    # Binomial is of the form: a*x*x + b*x + c
    # No error checking is performed.
    return a*x*x + b*x +c

y = binomial(5, 2, 3, 4)
print('y = 2*5*5 + 3*5 + 4 =', y)

# Default value c=1 is used.
y = binomial(5, 2, 3)
print('y = 2*5*5 + 3*5 + 1 =', y)

# Default values a=1, b=1, c=1 used
y = binomial(5)
print('y = 1*5*5 + 1*5 + 1 =', y)

            
Output:

y = 2*5*5 + 3*5 + 4 = 69
y = 2*5*5 + 3*5 + 1 = 66
y = 1*5*5 + 1*5 + 1 = 31

            

Consider the line:

y = binomial(5)

In this case the default values a = 1, b = 1 and c = 1 are used. This produces the call:

y = binomial(5, 1, 1, 1)

Variable-Length Argument Lists

There can be occasions when the number of arguments to be passed to a function is not known in advance. The textbook classic example is a function to calculate the average of a series of numbers where it is not known how many numbers will be in the series.

Syntax

    def function_name([*parameter]):
      statement(s)

Parameters:
    *parameter: List of zero, one 
                or more parameters.

          

From realpython.com:

When a parameter name in a Python function definition is preceded by an asterisk (*), it indicates argument tuple packing. Any corresponding arguments in the function call are packed into a tuple that the function can refer to by the given parameter name.

Example 7


# Demonstrates the use of variable-length argument lists
# in a user defined function.

import math

def median(*nums):
    # Returns the median of a series of numbers.
    # There may be zero, one or more numbers
    # passed to this function.
    
    # Retrieve the series of numbers as a list.
    list1 = list(nums)
    
    # Check that all values passed are numerical.
    # Median can only be calculated for numbers.
    if not all([isinstance(item, (int, float)) for item in list1]):
        print('Values are not all numerical:', list1)
        return None
    
    # The list of numbers must be sorted
    # to calculate the median.
    list1 = sorted(list1)
    print('Sorted values:', list1)
    
    # Now to calculate the median.
    length = len(list1)
    if length == 0:
        # No arguments passed by caller.
        return None
    elif (length % 2) == 0:
        # Even number of elements
        # Calculate average of middle two elements
        mid = (list1[length // 2] + list1[length // 2 - 1]) / 2
    else:
        # Odd number of elements
        # so return the middle element
        mid = (list1[length // 2])
    return mid

# Calculate some median values
print('Median value =', median(23, 76, 1, 987, 0))
print()
print('Median value =', median(float('23.56'), 76, 1, 
      min(987, 500, 783), math.pi, 29))
print()
print('Median value =', median(5))
print()
print('Median value =', median())
print()
print('Median value =', median('Spam', 4, 5.9))

          

Output:


Sorted values: [0, 1, 23, 76, 987]
Median value = 23

Sorted values: [1, 3.141593, 23.56, 29, 76, 500]
Median value = 26.28

Sorted values: [5]
Median value = 5

Sorted values: []
Median value = None

Values are not all numerical: ['Spam', 4, 5.9]

          

Example 6 Explanation

  • An argument list of 0, 1 or more numbers is passed to the function median(*nums). The numbers can be any expression that evaluates to an integer or float. The function checks that all arguments passed are numerical otherwise a median cannot be calculated. If one or more non-numerical argument has been passed then the function returns None.
     
  • The arguments are sorted in ascending order into a list.
     
  • A conditional statement checks whether:
    • The list is empty: The function returns the value None to the caller.
  • The list has an odd number of members:
    • The function returns the value of the list's middle element to the caller.
    • For example if the list has seven members, [2, 4, 6, 8, 10, 12, 14] the value of the fourth element (ie 8) is returned.
       
  • The list has an even number of elements:
    • In this case there is not a single middle element in the list. Instead, the two values around the middle of the list are averaged and this average is returned to the caller.
    • For example, if the list contained six members such as [2, 4, 6, 8, 10, 12] then the middle two elements are 6 and 8. The average of these two values is 7 and this is what would be passed back to the caller.

This example, simple as it is, demonstrates the power of variable-length argument lists.