© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
C. BellBeginning MicroPython with the Raspberry Pi Picohttps://doi.org/10.1007/978-1-4842-8135-2_3

3. How to Program in MicroPython

Charles Bell1  
(1)
Warsaw, VA, USA
 

Now that we have a basic understanding of the Pico and have had a short introduction to MicroPython, we are ready to learn more about programming in MicroPython. Mastering MicroPython is very easy, and some may suggest it doesn’t require any formal training to use. This is largely true and thus you should be able to write MicroPython scripts with only a little bit of knowledge about the language.

Given that MicroPython is a subset of Python, we can learn the basics of the Python language first through examples on our PC. Thus, this chapter presents a crash course on the basics of Python programming including an explanation about some of the most commonly used language features. As such, this chapter will provide you with the skills you need to understand the Python examples in this book and available on the Internet. The chapter also demonstrates how to program in Python through examples that you can run on your PC or your Pico. So, let’s get started!

Note

I use the term Python to describe programming concepts in this chapter that apply to both MicroPython and Python. Concepts unique to MicroPython use the term MicroPython.

Now let’s learn some of the basic concepts of Python programming. We will begin with the building blocks of the language such as variables, modules, and basic statements and then move into the more complex concepts of flow control and data structures. While the material may seem to come at you in a rush, this tutorial on Python covers only the most fundamental knowledge of the language and how to use it on your PC and Pico. It is intended to get you started writing Python applications quickly.

If you know the basics of Python programming, feel free to skim through this chapter. However, I recommend working through the example projects at the end of the chapter, especially if you’ve not written many Python applications.

The following sections present many of the basic features of Python programming that you will need to know to understand the example projects in this book.

Basic Concepts

Python is a high-level, interpreted, object-oriented scripting language. One of the biggest goals of Python is to have a clear, easy-to-understand syntax that reads as close to English as possible. That is, you should be able to read a Python script and understand it even if you haven’t learned the language. Python also has less punctuation (special symbols) and fewer syntactical machinations than other languages. The following lists a few of the key features of Python:
  • An interpreter processes python at runtime. No external (separate) compiler is used.

  • Python supports object-oriented programming constructs by way of a class.

  • Python is a great language for the beginner-level programmers and supports the development of a wide range of applications.

  • Python is a scripting language but can be used for a wide range of applications.

  • Python is very popular and used throughout the world giving it a huge support base.

  • Python has few keywords, simple structure, and a clearly defined syntax. This allows the student to pick up the language quickly.

  • Python code is more clearly defined and visible to the eyes.

Recall that Python is available for download (python.org/downloads) for just about every platform that you may encounter or use – even Windows! Python is a very easy language to learn with very few constructs that are even mildly difficult to learn. Rather than toss out a sample application, let’s approach learning the basics of Python in a Python-like way: one step at a time.

Code Blocks

The first thing you should learn is that Python does not use a code block demarcated with symbols like other languages. More specifically, code that is local to a construct such as a function or conditional or loop is designated using indentation. Thus, the lines below that are indented (by spaces or, gasp, tabs) so that the starting characters align for the code body of the construct.

Tip

Examples like the following are designed to show concepts in action. As such, some of the syntax is a mock-up (also called pseudo) Python code and may not execute if used verbatim.

if (expr1):
    print("inside expr1")
    print("still inside expr1")
else:
    print("inside else")
    print("still inside else")
print("in outer level")

Here, we see a conditional or if statement. Notice the function call print() is indented. This signals the interpreter that the lines belong to the construct above it. For example, the two print statements that mention expr1 form the code block for the if condition (and executes when the expression evaluates to true). Similarly, the next two print statements form the code block for the else condition. Finally, the non-indented lines are not part of the conditional and thus are executed after either the if or else depending on the expression evaluation.

As you can see, indentation is a key concept to learn when writing Python. Even though it is very simple, making mistakes in indentation can result in code executing that you did not expect or worse errors from the interpreter.

There is one special symbol that you will encounter frequently. Notice the use of the colon (:) in the preceding code. This symbol is used to terminate a construct and signals the interpreter that the declaration is complete, and the body of the code block follows. We use this for conditionals, loops, classes, and functions.

Note

I use “program” and “application” interchangeably with “script” when discussing Python. While, technically, Python code is a script, we often use it in contexts where “program” and “application” are more appropriate.

Comments

One of the most fundamental concepts in any programming language is the ability to annotate your source code with nonexecutable text that not only allows you to make notes among the lines of code but also forms a way to document your source code.

To add comments to your source code, use the pound sign (#). Place at least one at the start of the line to create a comment for that line, repeating the # symbols for each subsequent line. This creates what is known as a block comment as shown. Notice I used a comment without any text to create whitespace. This helps with readability and is a common practice for block comments:
#
# Beginning MicroPython – Chapter 3
#
# Example Python application.
#
# Created by Dr. Charles Bell
#
You can also place comments on the same line as the source code. The compiler will ignore anything from the pound sign to the end of the line. For example, the following shows a common style of documenting variables:
zip = 35012                # Zip or postal code
address1= "123 Main St."   # Store the street address

Arithmetic

You can perform many mathematical operations in Python including the usual primitives, but also logical operations and operations used to compare values. Rather than discuss these in detail, I provide a quick reference in Table 3-1 that shows the operation and example of how to use the operation.
Table 3-1

Arithmetic, Logical, and Comparison Operators in Python

Type

Operator

Description

Example

Arithmetic

+

Addition

int_var + 1

-

Subtraction

int_var - 1

*

Multiplication

int_var * 2

/

Division

int_var / 3

%

Modulus

int_var % 4

-

Unary subtraction

-int_var

+

Unary addition

+int_var

Logical

&

Bitwise and

var1&var2

|

Bitwise or

var1|var2

^

Bitwise exclusive or

var1^var2

~

Bitwise complement

~var1

and

Logical and

var1and var2

or

Logical or

var1or var2

Comparison

==

Equal

expr1==expr2

!=

Not equal

expr1!=expr2

<

Less than

expr1<expr2

>

Greater than

expr1>expr2

<=

Less than or equal

expr1<=expr2

>=

Greater than or equal

expr1>=expr2

Bitwise operations produce a result on the values performed on each bit. Logical operators (and, or) produce a value that is either true or false and are often used with expressions or conditions.

Output to Screen

We’ve already seen a few examples of how to print messages to the screen but without any explanation about the statements shown. While it is unlikely that you would print output from your MicroPython board for projects that you deploy, learning Python is much easier when you display messages to the screen.

Some of the things you may want to print are variable values, which is to communicate what is going on inside your program. This can include simple messages (strings), but can also include the values of variables, expressions, and more.

As we have seen, the built-in print() function is the most common way to display output text contained within single or double quotes. We have also seen some interesting examples using another function named format(). The format() function generates a string for each argument passed. These arguments can be other strings, expressions, variables, etc. The function is used with a special string that contains replacement keys delimited by curly braces { } (called string interpolation1). Each replacement key contains either an index (starting at 0) or a named keyword. The special string is called a format string. Let’s see a few examples to illustrate the concept. You can run these yourself either on your PC or your MicroPython board. I include the output so you can see what each statement does:
>>> a = 42
>>> b = 1.5
>>> c = "seventy"
>>> print("{0} {1} {2} {3}".format(a,b,c,(2+3)))
42 1.5 six 5
>>> print("{a_var} {b_var} {c_var}{0}".format((3*3),c_var=c,b_var=b,a_var=a))
42 1.5 six 9

Notice I created three variables (we will talk about variables in the next section) assigning them different values with the equal symbol (=). I then printed a message using a format string with four replacement keys labeled using an index. Notice the output of that print statement. Notice I included an expression at the end to show how the format() function evaluates expressions.

The last line is more interesting. Here, I use three named parameters (a_var, b_var, c_var) and used a special argument option in the format() function where I assign the parameter a value. Notice I listed them in a different order. This is the greatest advantage of using named parameters; they can appear in any order but are placed in the format string in the position indicated.

As you can see, it’s just a case of replacing the { } keys with those from the format() function, which converts the arguments to strings. We use this technique anywhere we need a string that contains data gathered from more than one area. We can see this in the preceding examples.

Tip

See https://docs.python.org/3/library/string.html#formatstrings for more information about format strings.

Now let’s look at how we can use variables in our programs (scripts).

Tip

For those who have learned to program in another language like C or C++, Python allows you to terminate a statement with the semicolon (;); however, it is not needed and considered bad form to include it.

Variables and Statements

Python is a dynamically typed language, which means the type of the variable (the type of data it can store) is determined by context as it is encountered or used. This contrasts with other languages such as C and C++ where you must declare the type before you use the variable.

Variables in Python are simply named memory locations that you can use to store values during execution. We store values by using the equal sign to assign the value. Python variable names can be anything you want, but there are rules and conventions most Python developers follow. The rules are listed in the Python coding standard.2

Tip

See www.python.org/dev/peps/pep-0008 for the PEP8 coding guidelines and complete list of the rules and standards.

However, the general, overriding rule requires variable names that are descriptive, have meaning in context, and can be easily read. That is, you should avoid names with random characters, forced abbreviations, acronyms, and similar obscure names. By convention, your variable names should be longer than a single character (with some acceptable exceptions for loop counting variables) and short enough to avoid overly long code lines.

WHAT IS A LONG CODE LINE?

Most will say a code line should not exceed 80 or even 100 characters, but this harkens from the darker days of programming when we used punched cards that permitted a maximum of 80 characters per card and later display devices with the same limitation. With modern, widescreen displays, this is not as big a deal, but I still recommend keeping lines short to ensure better readability. No one likes to scroll down to read!

Thus, there is a lot of flexibility in what you can name your variables. There are additional rules and guidelines in the PEP8 standard, and should you wish to bring your project source code up to date with the standards, you should review the PEP8 naming standards for functions, classes, and more.

The following shows some examples of simple variables and their dynamically determined types:
# floating point number
length = 10.0
# integer
width = 4
# string
box_label = "Tools"
# list
car_makers = ['Ford', 'Chevrolet', 'Dodge']
# tuple
porsche_cars = ('911', 'Cayman', 'Boxster')
# dictionary
address = {"name": "Joe Smith", "Street": "123 Main", "City": "Anytown", "State": "New Happyville"}

So, how did we know the variable width is an integer? Simply because the number 4 is an integer. Likewise, Python will interpret “Tools” as a string. We’ll see more about the last three types and other types supported by Python in the next section.

Types

As mentioned, Python does not have a formal type of specification mechanism like other languages. However, you can still define variables to store anything you want. In fact, Python permits you to create and use variables based on context, and you can use initialization to “set” the data type for the variable. The following shows several examples:
# Numbers
float_value = 9.75
integer_value = 5
# Strings
my_string = "He says, he's already got one."
print("Floating number: {0}".format(float_value))
print("Integer number: {0}".format(integer_value))
print(my_string)
For situations where you need to convert types or want to be sure values are typed a certain way, there are many functions for converting data. Table 3-2 shows a few of the more commonly used type conversion functions. I discuss some of the data structures in a later section.
Table 3-2

Type Conversion in Python

Function

Description

int(x [,base])

Converts x to an integer. Base is optional (e.g., 16 for hex)

long(x [,base])

Converts x to a long integer

float(x)

Converts x to a floating-point

str(x)

Converts object x to a string

tuple(t)

Converts t to a tuple

list(l)

Converts l to a list

set(s)

Converts s to a set

dict(d)

Creates a dictionary

chr(x)

Converts an integer to a character

hex(x)

Converts an integer to a hexadecimal string

oct(x)

Converts an integer to an octal string

However, you should use these conversion functions with care to avoid data loss or rounding. For example, converting a float to an integer can result in truncation. Likewise, printing floating-point numbers can result in rounding.

Now let’s look at some commonly used data structures including this strange thing called a dictionary.

Basic Data Structures

What you have learned so far about Python is enough to write the most basic programs and indeed more than enough to tackle the example project later in this chapter. However, when you start needing to operate on data – either from the user or from sensors and similar sources – you will need a way to organize and store data as well as perform operations on the data in memory. The following introduces three data structures in order of complexity: lists, tuples, and dictionaries.

Lists

Lists are a way to organize data in Python. It is a free-form way to build a collection. That is, the items (or elements) need not be the same data type. Lists also allow you to do some interesting operations such as adding things at the end, beginning, or at a special index. The following demonstrates how to create a list:
# List
my_list = ["abacab", 575, "rex, the wonder dog", 24, 5, 6]
my_list.append("end")
my_list.insert(0,"begin")
for item in my_list:
  print("{0}".format(item))
Here, we see I created the list using square brackets ([]). The items in the list definition are separated by commas. Note that you can create an empty list simply by setting a variable equal to []. Since lists, like other data structures, are objects, there are several operations available for lists such as the following:
  • append(x): Add x to the end of the list

  • extend(l): Add all items to the end of the list

  • insert(pos,item): Insert an item at a position pos

  • remove(value): Remove the first item that matches (==) the value

  • pop([i]): Remove and return the item at position i or end of list

  • index(value): Return the index of the first item that matches

  • count(value): Count occurrences of the value

  • sort(): Sort the list (ascending)

  • reverse(): Reverse sort the list

Lists are like arrays in other languages and very useful for building dynamic collections of data.

Tuples

Tuples, on the other hand, are a more restrictive type of collection. That is, they are built from a specific set of data and do not allow manipulation like a list. In fact, you cannot change the elements in the tuple. Thus, we can use tuples for data that should not change. The following shows an example of a tuple and how to use it:
# Tuple
my_tuple = (0,1,2,3,4,5,6,7,8,"nine")
for item in my_tuple:
  print("{0}".format(item))
if 7 in my_tuple:
  print("7 is in the list")
Here, we see I created the tuple using parentheses (). The items in the tuple definition are separated by commas. Note that you can create an empty tuple simply by setting a variable equal to (). Since tuples, like other data structures, are objects, there are several operations available such as the following, including operations for sequences such as inclusion, location, etc.:
  • x in t: Determine if t contains x

  • x not in t: Determine if t does not contain x

  • s + t: Concatenate tuples

  • s[i]: Get element i

  • len(t): Length of t (number of elements)

  • min(t): Minimal (smallest value)

  • max(t): Maximal (largest value)

If you want even more structure with storing data in memory, you can use a special construct (object) called a dictionary.

Dictionaries

A dictionary is a data structure that allows you to store key, value pairs where the data is assessed via the keys. Dictionaries are a very structured way of working with data and the most logical form we will want to use when collecting complex data. The following shows an example of a dictionary:
# Dictionary
my_dictionary = {
  'first_name': "Chuck",
  'last_name': "Bell",
  'age': 36,
  'my_ip': (192,168,1,225),
  42: "What is the meaning of life?",
}
# Access the keys:
print(my_dictionary.keys())
# Access the items (key, value) pairs
print(my_dictionary.items())
# Access the values
print(my_dictionary.values())
# Create a list of dictionaries
my_addresses = [my_dictionary]

There is a lot going on here! We see a basic dictionary declaration that uses curly braces to create a dictionary. Inside that, we can create as many key, value pairs we want separated by commas. Keys are defined using strings (I use single quotes by convention, but double quotes will work) or integers, and values can be any data type we want. For the my_ip attribute, we are also storing a tuple.

Following the dictionary, we see several operations performed on the dictionary from printing the keys, printing all the values, and printing only the values. The following shows the output of executing this code snippet from the Python interpreter:
[42, 'first_name', 'last_name', 'age', 'my_ip']
[(42, 'what is the meaning of life?'), ('first_name', 'Chuck'), ('last_name', 'Bell'), ('age', 36), ('my_ip', (192, 168, 1, 225))]
['what is the meaning of life?', 'Chuck', 'Bell', 36, (192, 168, 1, 225)]
'42': what is the meaning of life?
'first_name': Chuck
'last_name': Bell
'age': 36
'my_ip': (192, 168, 1, 225)
As we have seen in this example, there are several operations (functions or methods) available for dictionaries including the following. Together, this list of operations makes dictionaries a very powerful programming tool:
  • len(d): Number of items in d

  • d[k]: Item of d with key k

  • d[k] = x: Assign key k with value x

  • del d[k]: Delete an item with key k

  • k in d: Determine if d has an item with key k

  • d.items(): Return a list (view) of the (key, value) pairs in d

  • d.keys(): Return a list (view) of the keys in d

  • d.values(): Return a list (view) of the values in d

Best of all, objects can be placed inside other objects. For example, you can create a list of dictionaries like I did earlier, a dictionary that contains lists and tuples, and any combination you need. Thus, lists, tuples, and dictionaries are a powerful way to manage data for your program.

In the next section, we will learn how we can control the flow of our programs.

Statements

Now that we know more about the basics of Python, we can discover some of the more complex code concepts you will need to complete your project such as conditional statements and loops.

Conditional Statements

We have also seen some simple conditional statements – statements designed to alter the flow of execution depending on the evaluation of one or more expressions. Conditional statements allow us to direct execution of our programs to sections (blocks) of code based on the evaluation of one or more expressions. The conditional statement in Python is the if statement.

We have seen the if statement in action in our example code. Notice in the example, we can have one or more (optional) else phrases that we execute once the expression for the if conditions evaluate to false. We can chain if/else statements to encompass multiple conditions where the code executed depends on the evaluation of several conditions. The following shows the general structure of the if statement. Notice in the comments how I explain how execution reaches the body of each condition:
if (expr1):
    # execute only if expr1 is true
elif ((expr2) or (expr3)):
    # execute only if expr1 is false *and* either expr2 or expr3 is true
else:
    # execute if both sets of if conditions evaluate to false

While you can chain the statement as much as you want, use some care here because the more elif sections you have, the harder it will become to understand, maintain, and avoid logic errors in your expressions.

There is another form of conditional statement called a ternary operator. Ternary operators are more commonly known as conditional expressions in Python. These operators evaluate something based on a condition being true or not. They became a part of Python in version 2.4. Conditional expressions are a shorthand notation for an if-then-else construct used (typically) in an assignment statement as shown in the following:
variable = value_if_true if condition else value_if_false
Here, we see if the condition is evaluated to true, the value preceding the if is used, but if the condition evaluates to false, the value following the else is used. The following shows a short example:
>>> numbers = [1,2,3,4]
>>> for n in numbers:
...   x = 'odd' if n % 2 else 'even'
...   print("{0} is {1}.".format(n, x))
...
1 is odd.
2 is even.
3 is odd.
4 is even.
>>>

Conditional expressions allow you to quickly test a condition instead of using a multiline conditional statement, which can help make your code a bit easier to read (and shorter).

Loops

Loops are used to control the repetitive execution of a block of code. There are three forms of loops that have slightly different behaviors. All loops use conditional statements to determine whether to repeat execution or not. That is, they repeat as long as the condition is true. The two types of loops are while and for. I explain each with an example.

The while loop has its condition at the “top” or start of the block of code. Thus, while loops only execute the body if and only if the condition evaluates to true on the first pass. The following illustrates the syntax for a while loop. This form of loop is best used when you need to execute code only if some expression(s) evaluate to true. For example, iterating through a collection of things whose number of elements is unknown (loop until we run out of things in the collection):
while (expression):
   # do something here
For loops are sometimes called counting loops because of their unique form. For loops allow you to define a counting variable and a range or list to iterate over. The following illustrates the structure of the for loop. This form of loop is best used for performing an operation in a collection. In this case, Python will automatically place each item in the collection in the variable for each pass of the loop until no more items are available:
for variable_name in list:
  # do something here
You can also do range loops or counting loops. This uses a special function called range() that takes up to three parameters, range([start], stop[, step]), where start is the starting number (an integer), stop is the last number in the series, and step is the increment. So, you can count by 1, 2, 3, etc. through a range of numbers. The following shows a simple example:
for i in range(2,9):
   # do something here

There are other uses for range() that you may encounter. See the documentation on this function and other built-in functions at https://docs.python.org/3/library/functions.html for more information.

Python also provides a mechanism for controlling the flow of the loop (e.g., duration or termination) using a few special keywords as follows:
  • break: Exit the loop body immediately

  • continue: Skip to the next iteration of the loop

  • else: Execute code when the loop ends (not executed if the loop was stopped with a break statement)

There are some uses for these keywords, particularly break, but it is not the preferred method of terminating and controlling loops. That is, professionals believe the conditional expression or error handling code should behave well enough to not need these options.

Modularization

The last group of topics are the most advanced and include modularization (code organization). As we will see, we can use functions to group code, to eliminate duplication, and to encapsulate functionality into objects.

Including Modules

Python applications can be built from reusable libraries that are provided by the Python environment. They can also be built from custom modules or libraries that you create yourself or download from a third party. These are often distributed as a set of Python code files (e.g., files that have a file extension of .py). When we want to use a library (function, class, etc.) that is included in a module, we use the import keyword and list the name of the module. The following shows some examples:
import os
import sys

The first two lines demonstrate how to import a base or common module provided by Python. In this case, we are using or importing modules for the os and sys modules (operating system and Python system functions).

Tip

It is customary to list your imports in alphabetical order with built-in modules first, then third-party modules listed next, and finally your own modules.

Functions

Python allows you to use modularization in your code. While it supports object-oriented programming by way of classes (a more advanced feature that you are unlikely to encounter for most Python GPIO examples), on a more fundamental level you can break your code into smaller chunks using functions.

Functions use a special keyword construct (rare in Python) to define a function. We simply use def followed by a name for the function and a comma-separated list of parameters in parentheses. The colon is used to terminate the declaration. The following shows an example:
def print_dictionary(the_dictionary):
    for key, value in the_dictionary.items():
      print("'{0}': {1}".format(key, value))
# define some data
my_dictionary = {
  'name': "Chuck",
  'age': 37,
}

You may be wondering what this strange code does. Notice the loop is assigning two values from the result of the items() function. This is a special function available from the dictionary object. The items() function returns the key, value pairs, hence the names of the variables.

The next line prints out the values. The use of formatting strings where the curly braces define the parameter number starting at 0 is common for Python 3 applications. See the Python documentation for more information about formatting strings (https://docs.python.org/3/library/string.html#format-string-syntax).

The body of the function is indented. All statements indented under this function declaration belong to the function and are executed when the function is called. We can call functions by name providing any parameters as follows. Notice how I referenced the values in the dictionary by using the key names:
print_dictionary(my_dictionary)
print(my_dictionary['age'])
print(my_dictionary['name'])
This example together with the preceding code, when executed, generates the following:
$ python
Python 3.9.6 (tags/v3.9.6:db3ff76, Jun 28 2021, 15:26:21) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> def print_dictionary(the_dictionary):
...     for key, value in the_dictionary.items():
...       print("'{0}': {1}".format(key, value))
...
>>> # define some data
>>> my_dictionary = {
...   'name': "Chuck",
...   'age': 37,
... }
>>> print_dictionary(my_dictionary)
'name': Chuck
'age': 37
>>> print(my_dictionary['age'])
37
>>> print(my_dictionary['name'])
Chuck
>>>

Now let’s look at the most complex concept in Python – object-oriented programming.

Classes and Objects

You may have heard that Python is an object-oriented programming language. But what does that mean? Simply, Python is a programming language that provides facilities for describing objects (things) and what you can do with the object (operations).

Objects are an advanced form of data abstraction where the data is hidden from the caller and only manipulated by the operations (methods) the object provides. But before we start learning object-oriented programming in Python, let’s look at some of the terminology common to the technique.

Object-Oriented Programming (OOP) Terminology

Like any technology or concept, there comes a certain number of terms that you must learn to be able to understand and communicate with others about the technology. We’ve seen some of these thus far in the book. The following briefly describes some of the terms you will need to know to learn more about object-oriented programming:
  • Attribute: A data element in a class.

  • Class: A code construct used to define an object in the form of attributes (data) and methods (functions) that operate on the data. Methods and attributes in Python can be accessed using dot notation.

  • Class instance variable: A variable that is used to store an instance of an object. They are used like any other variable and, combined with dot notation, allow us to manipulate objects.

  • Constructor: A special method that is executed once each time the class is instantiated. Thus, you would place any startup or initialization code you need to run once in the constructor.

  • Instance: An executable form of a class created by assigning a class to a variable initializing the code as an object.

  • Inheritance: The inclusion of attributes and methods from one class in another.

  • Instantiation: The creation of an instance of a class.

  • Method overloading: The creation of two or more methods with the same name but with a different set of parameters. This allows us to create methods that have the same name but may operate differently depending on the parameters passed.

  • Polymorphism: Inheriting attributes and methods from a base class adding additional methods or overriding (changing) methods.

There are many more OOP terms, but these are the ones you will encounter most often.

Python Object Syntax

The syntax we use in Python is the class statement, which you can use to help make your projects modular. By modular, we mean the source code is arranged to make it easier to develop and maintain. Typically, we place classes in separate modules (code files), which helps organize the code better. While it is not required, I recommend using this technique of placing a class in its own source file. This makes modifying the class or fixing problems (bugs) easier.

So, what are Python classes? Let’s begin by considering the construct as an organization technique. We can use the class to group data and methods together. The name of the class immediately follows the keyword class followed by a colon. You declare other class methods like any other method except the first argument must be self, which ties the method to the class instance when executed.

Function Vs. Method – What is The Difference?

I prefer to use terms that have been adopted by the language designers or community of developers. For example, some use “function,” but others may use “method.” Still others may use subroutine, routine, procedure, etc. It doesn’t matter which term you use, but you should strive to use terms consistently. One example, which can be confusing to some, is I use the term method when discussing object-oriented examples. That is, a class has methods, not functions. However, you can use function in place of method, and you’d still be correct (mostly).

Accessing the data is done using one or more methods by using the class (creating an instance) and using dot notation to reference the data member or function. Let’s look at an example. Listing 3-1 shows a complete class that describes (models) the most basic characteristics of a vehicle used for transportation. I created a file named vehicle.py to contain this code.
#
# Beginning MicroPython – Chapter 3
#
# Class Example: A generic vehicle
#
# Dr. Charles Bell
#
class Vehicle:
    """Base class for defining vehicles"""
    axles = 0
    doors = 0
    occupants = 0
    def __init__(self, num_axles, num_doors):
        self.axles = num_axles
        self.doors = num_doors
    def get_axles(self):
        return self.axles
    def get_doors(self):
        return self.doors
    def add_occupant(self):
        self.occupants += 1
    def num_occupants(self):
        return self.occupants
Listing 3-1

Vehicle Class

Notice a couple of things here. First, there is a method with the name __init__(). This is the constructor and is called when the class instance is created. You place all your initialization code like setting variables in this method. We also have methods for returning the number of axles, doors, and occupants. We have one method in this class to add occupants.

Also notice we address each of the class attributes (data) using self.<name>. This is how we can ensure we always access the data that is associated with the instance created.

Let’s see how this class can be used to define a family sedan. Listing 3-2 shows code that uses this class. We can place this code in a file named sedan.py.
#
# Beginning MicroPython – Chapter 3
#
# Class Example: Using the generic Vehicle class
#
# Dr. Charles Bell
#
from vehicle import Vehicle
sedan = Vehicle(2, 4)
sedan.add_occupant()
sedan.add_occupant()
sedan.add_occupant()
print("The car has {0} occupants.".format(sedan.num_occupants()))
Listing 3-2

Using the Vehicle Class

Notice the first line imports the Vehicle class from the vehicle module. Notice I capitalized the class name but not the file name. This is a very common naming scheme. Next in the code, we create an instance of the class. Notice I passed in 2, 4 to the class name. This will cause the __init__() method to be called when the class is instantiated.

The variable, sedan, becomes the class instance variable (object) that we can manipulate, and I do so by adding three occupants and then printing out the number of occupants using the method in the Vehicle class.

We can run the code on our PC using the following command. As we can see, it tells us there are three occupants in the vehicle when the code is run. Nice.
$ python ./sedan.py
The car has 3 occupants.
Now, let’s see how we can use the vehicle class to demonstrate inheritance. In this case, we will create a new class named PickupTruck that uses the vehicle class but adds specialization to the resulting class. Listing 3-3 shows the new class. I placed this code in a file named pickup_truck.py. As you will see, a pickup truck is a type of vehicle.
#
# Beginning MicroPython – Chapter 3
#
# Class Example: Inheriting the Vehicle class to form a
# model of a pickup truck with maximum occupants and maximum
# payload.
#
# Dr. Charles Bell
#
from vehicle import Vehicle
class PickupTruck(Vehicle):
    """This is a pickup truck that has:
    axles = 2,
    doors = 2,
    __max occupants = 3
    The maximum payload is set on instantiation.
    """
    occupants = 0
    payload = 0
    max_payload = 0
    def __init__(self, max_weight):
        super().__init__(2,2)
        self.max_payload = max_weight
        self.__max_occupants = 3
    def add_occupant(self):
        if self.occupants < self.__max_occupants:
            super().add_occupant()
        else:
            print("Sorry, only 3 occupants are permitted in the truck.")
    def add_payload(self, num_pounds):
        if (self.payload + num_pounds) < self.max_payload:
            self.payload += num_pounds
        else:
            print("Overloaded!")
    def remove_payload(self, num_pounds):
        if (self.payload - num_pounds) >= 0:
            self.payload -= num_pounds
        else:
            print("Nothing in the truck.")
    def get_payload(self):
        return self.payload
Listing 3-3

Pickup Truck Class

Notice a few things here. First, notice the class statement: class PickupTruck(Vehicle):. When we want to inherit from another class, we add the parentheses with the name of the base class. This ensures Python will use the base class, allowing the derived class to use all its accessible data and memory. If you want to inherit from more than one class, you can (called multiple inheritance), just list the base (parent) classes with a comma-separated list.

Next, notice the __max_occupants variable. Using two underscores in a class for an attribute or a method name, makes it private to the class. That is, it should only be accessed from within the class. No caller of the class (via a class variable/instance) can access the private items nor can any class that was derived from the class. It is always a good practice to hide the attributes (data).

You may be wondering what happened to the occupant methods. Why aren’t they in the new class? They aren’t there because our new class inherited all that behavior from the base class. Not only that, but the code has been modified to limit occupants to exactly three occupants.

I also want to point out the documentation I added to the class. We use documentation strings (strings that use a set of three double quotes before and after) to document the class. You can put documentation here to explain the class and its methods. We’ll see a good use of this a bit later.

Finally, notice the code in the constructor. This demonstrates how to call the base class method, which I do to set the number of axles and doors. We could do the same in other methods if we wanted to call the base class method’s version.

Now, let’s write some code to use this class. Listing 3-4 shows the code we used to test this class. Here, we create a file named pickup.py that creates an instance of the pickup truck, adds occupants, and payload, then prints out the contents of the truck.
#
# Beginning MicroPython – Chapter 3
#
# Class Example: Exercising the PickupTruck class.
#
# Dr. Charles Bell
#
from pickup_truck import PickupTruck
pickup = PickupTruck(500)
pickup.add_occupant()
pickup.add_occupant()
pickup.add_occupant()
pickup.add_occupant()
pickup.add_payload(100)
pickup.add_payload(300)
print("Number of occupants in truck = {0}.".format(pickup.num_occupants()))
print("Weight in truck = {0}.".format(pickup.get_payload()))
pickup.add_payload(200)
pickup.remove_payload(400)
pickup.remove_payload(10)
Listing 3-4

Using the PickupTruck Class

Notice I add a couple of calls to the add_occupant() method, which the new class inherits and overrides. I also add calls so that we can test the code in the methods that check for excessive occupants and maximum payload capacity. When we run this code, we will see the results as shown in the following:
$ python ./pickup.py
Sorry, only 3 occupants are permitted in the truck.
Number of occupants in truck = 3.
Weight in truck = 400.
Overloaded!
Nothing in the truck.

Once again, I ran this code on my PC, but I can run all this code on the MicroPython board and will see the same results.

There is one more thing we should learn about classes: built-in attributes. Recall the __init__() method. Python automatically provides several built-in attributes each starting with __ that you can use to learn more about objects. The following lists a few of the operators available for classes:
  • __dict__: Dictionary containing the class namespace

  • __doc__: Class documentation string

  • __name__: Class name

  • __module__: Module name where the class is defined

  • __bases__: The base class(es) in order of inheritance

The following shows what each of these attributes returns for the preceding PickupTruck class. I added this code to the pickup.py file:
print("PickupTruck.__doc__:", PickupTruck.__doc__)
print("PickupTruck.__name__:", PickupTruck.__name__)
print("PickupTruck.__module__:", PickupTruck.__module__)
print("PickupTruck.__bases__:", PickupTruck.__bases__)
print("PickupTruck.__dict__:", PickupTruck.__dict__)
When this code is run, we see the following output:
Sorry, only 3 occupants are permitted in the truck.
Number of occupants in truck = 3.
Weight in truck = 400.
Overloaded!
Nothing in the truck.
PickupTruck.__doc__: This is a pickup truck that has:
    axles = 2,
    doors = 2,
    __max occupants = 3
    The maximum payload is set on instantiation.
PickupTruck.__name__: PickupTruck
PickupTruck.__module__: pickup_truck
PickupTruck.__bases__: (<class 'vehicle.Vehicle'>,)
PickupTruck.__dict__: {'__module__': 'pickup_truck', '__doc__': 'This is a pickup truck that has:     axles = 2,     doors = 2,     __max occupants = 3     The maximum payload is set on instantiation.     ', 'occupants': 0, 'payload': 0, 'max_payload': 0, '__init__': <function PickupTruck.__init__ at 0x0000023ADF7B7C10>, 'add_occupant': <function PickupTruck.add_occupant at 0x0000023AE1150820>, 'add_payload': <function PickupTruck.add_payload at 0x0000023AE11508B0>, 'remove_payload': <function PickupTruck.remove_payload at 0x0000023AE1150940>, 'get_payload': <function PickupTruck.get_payload at 0x0000023AE11509D0>}

You can use the built-in attributes whenever you need more information about a class. Notice the _PickupTruck__max_occupants entry in the dictionary. Recall that we made a pseudo-private variable, __max_occupants. Here, we see how Python refers to the variable by prepending the class name to the variable. Remember, variables that start with two underscores (not one) should be considered private to the class and only usable from within the class.

Tip

See https://docs.python.org/3/tutorial/classes.html for more information about classes in Python.

Now, let’s see a few examples of Python programs that we can use to practice. Like the previous examples, you can write and execute these either on your PC or on your MicroPython board.

Learning Python by Example

The best way to learn how to program in any language is practicing with examples. In this section, I present several examples that you can use to practice coding in Python. You can use either your Pico or your PC to run these examples. I present the first two examples using my PC and the second two using the Pico.

Tip

For the adventurous, I also include some challenges (modifications to the code) for you to work out on your own. I encourage you to try these so that you can get more hands-on experience coding in Python.

How Do I Create and Execute Python Files?

I also use Thonny to create the file, save it, and execute it. However, if you’d rather use a different editor, you may do so and execute the scripts using the python command as shown in the examples.

To open a new file in Thonny, click the FileNew menu, press CTRL+N, or click the New button on the far left of the toolbar. Recall, we can save the file using the FileSave menu, press CTRL+S, or click the Save button on the toolbar (third from the left and looks like a diskette3). Finally, to run the script in the current open tab in the editor window, click the RunRun current script menu, press F5, or click the Run button on the toolbar (fourth from the left and looks like a green dot with an arrow in the center).

I explain the code in detail for each example and show example output when you execute the code as well as a challenge for you to try a modification or two of each example on your own. I encourage you to implement these examples and figure out the challenge yourself as practice for the projects later in this book.

Example 1: Using Loops

This example demonstrates how to write loops in Python using the for loop. The problem we are trying to solve is converting integers from decimal to binary, hexadecimal, and octal. Often with hardware projects, we need to see values in one or more of these formats, and in some cases, the sensors we use (and the associated documentation) use hexadecimal rather than decimal. Thus, this example can be helpful in the future not only for how to use the for loop but also how to convert integers into different formats.

Write the Code

The example begins with a tuple of integers to convert. Tuples and lists can be iterated through (values read in order) using a for loop. Recall a tuple is read only, so in this case since it is input, it is fine, but in other cases where you may need to change values, you will want to use a list. Recall, the syntactical difference between a tuple and a list is the tuple uses parentheses and a list uses square brackets.

The for loop demonstrated here is called a “for each” loop. Notice I used the syntax “for value in values,” which tells Python to iterate over the tuple named values fetching (storing) each item into the value variable each iteration through the tuple.

Finally, I use the print() and format() functions to replace two placeholders {0} and {1} to print out a different format of the integer using the methods bin() for binary, oct() for octal, and hex() for hexadecimal that do the conversion for us.
#
# Beginning MicroPython – Chapter 3
#
# Example: Convert integer to binary, hex, and octal
#
# Dr. Charles Bell
#
# Create a tuple of integer values
values = (12, 450, 1, 89, 2017, 90125)
# Loop through the values and convert each to binary, hex, and octal
for value in values:
    print("{0} in binary is {1}".format(value, bin(value)))
    print("{0} in octal is {1}".format(value, oct(value)))
    print("{0} in hexadecimal is {1}".format(value, hex(value)))
Listing 3-5

Converting Integers

Execute the Code

You can save this code in a file named conversions.py on your PC and then open a terminal (console window) and run the code with the command python ./conversions.py (or python3 if you have multiple versions of Python installed). Figure 3-1 shows the code executing in Thonny.
Figure 3-1

Executing Python in Thonny

Listing 3-6 shows the command and output if you choose to run the code from the command line.
$ python ./conversions.py
12 in binary is 0b1100
12 in octal is 0o14
12 in hexadecimal is 0xc
450 in binary is 0b111000010
450 in octal is 0o702
450 in hexadecimal is 0x1c2
1 in binary is 0b1
1 in octal is 0o1
1 in hexadecimal is 0x1
89 in binary is 0b1011001
89 in octal is 0o131
89 in hexadecimal is 0x59
2017 in binary is 0b11111100001
2017 in octal is 0o3741
2017 in hexadecimal is 0x7e1
90125 in binary is 0b10110000000001101
90125 in octal is 0o260015
90125 in hexadecimal is 0x1600d
Listing 3-6

Conversions Example Output

Notice all the values in the tuple were converted.

Note

The rest of the examples in this chapter will use a listing of the output for brevity and readability.

Your Challenge

To make this example better, instead of using a static tuple to contain hard-coded integers, rewrite the example to read the integer from arguments on the command line along with the format. For example, the code would be executed like the following:
$ python ./conversions.py 123 hex
123 in hexadecimal is 0x7b
To read arguments from the command line, use argparse (https://docs.python.org/3/howto/argparse.html). If you want to read the integer from the command line, you can use the argparse module to add an argument by name as follows:
import argparse
# Setup the argument parser
parser = argparse.ArgumentParser()
# We need two arguments: integer, and conversion
parser.add_argument("original_val")
parser.add_argument("conversion")
# Get the arguments
args = parser.parse_args()

When you use the argument parser (argparse) module, the values of the arguments are all strings, so you will need to convert the value to an integer before you use the bin(), hex(), or oct() method.

You will also need to determine which conversion is requested. I suggest use only hex, bin, and oct for the conversion and use a set of conditions to check the conversion requested. Something like the following would work:
if args.conversion == 'bin':
    # do conversion to binary
elif args.conversion == 'oct':
    # do conversion to octal
elif args.conversion == 'hex':
    # do conversion to hexadecimal
else:
    print("Sorry, I don't understand, {0}.".format(args.conversion))

Notice the last else communicates that the argument was not recognized. This helps to manage user error.

There is one more thing about the argument parser you should know. You can pass in a help string when adding arguments. The argument parser also gets you the help argument (-h) for free. Observe the following. Notice I added a couple of strings using the help= parameter:
# We need two arguments: integer, and conversion
parser.add_argument("original_val", help="Value to convert.")
parser.add_argument("conversion", help="Conversion options: hex, bin, or oct.")
Now when we complete the code and run it with the -h option, we get the following output. Cool, eh?
$ python3 ./conversions.py -h
usage: conversions.py [-h] original_val conversion
positional arguments:
  original_val  Value to convert.
  conversion    options: hex, bin, or oct.
optional arguments:
  -h, --help    show this help message and exit

Example 2: Using Complex Data and Files

This example demonstrates how to work with the JavaScript Object Notation4 (JSON) in Python. In short, JSON is a markup language used to exchange data. Not only is it human readable, but it can also be used directly in your applications to store and retrieve data to and from other applications, servers, and even MySQL. In fact, JSON looks familiar to programmers because it resembles other markup schemes. JSON is also very simple in that it supports only two types of structures: (1) a collection containing (name, value) pairs and (2) an ordered list (or array). Of course, you can also mix and match the structures in an object. When we create a JSON object, we call it a JSON document.

The problem we are trying to solve is writing and reading data to/from files. In this case, we will use a special JSON encoder and decoder module named json that allows us to easily convert data in files (or other streams) to and from JSON. As you will see, accessing JSON data is easy by simply using the key (sometimes called fields) names to access the data. Thus, this example can be helpful in the future not only for how to use read and write files but also how to work with JSON documents.

Write the Code

This example stores and retrieves data in files. The data is basic information about pets including the name, age, breed, and type. The type is used to determine broad categories like fish, dog, or cat.

We begin by importing the JSON module (named json), which is built-in to the MicroPython platform. Next, we prepare some initial data by building JSON documents and storing them in a Python list. We use the json.loads() method to pass in a JSON formatted string. The result is a JSON document that we can add to our list. The examples use a very simple form of JSON documents – a collection of (name, value) pairs. The following shows an example of one of the JSON formatted strings used:
{"name":"Violet", "age": 11, "breed":"dachshund", "type":"dog"}

Notice we enclose the string inside curly braces and use a series of key names, a colon, and a value separated by commas. If this looks familiar, it’s because it is the same format as a Python dictionary. This demonstrates my comment that JSON syntax looks familiar to programmers.

The JSON method, json.loads(), takes the JSON formatted string and then parses the string checking for validity and returns a JSON document. We then store that document in a variable and add it to the list as shown in the following:
parsed_json = json.loads('{"name":"Violet", "age": 11, "breed":"dachshund", "type":"dog"}')
pets.append(parsed_json)

Once the data is added to the list, we then write the data to a file named my_data.json. To work with files, we first open the file with the open() function, which takes a file name (including a path if you want to put the file in a directory) and an access mode. We use “r” for read and “w” for write. You can also use “a” for append if you want to open a file and add to the end. Note that the “w” access will overwrite the file when you write to it. If the open() function succeeds, you get a file object that permits you to call additional functions to read or write data. The open() will fail if the file is not present (and you have requested read access) or you do not have permissions to write to the file.

In case you’re curious what the other access modes are, Table 3-3 shows the list of modes available for the open() function.
Table 3-3

Python File Access Modes

Mode

Description

R

Opens a file for reading only. The file pointer is placed at the beginning of the file. This is the default mode

Rb

Opens a file for reading only in binary format. The file pointer is placed at the beginning of the file. This is the default mode

r+

Opens a file for both reading and writing. The file pointer is placed at the beginning of the file

rb+

Opens a file for both reading and writing in binary format. The file pointer is placed at the beginning of the file

W

Opens a file for writing only. Overwrites the file if the file exists. If the file does not exist, creates a new file for writing

wb

Opens a file for writing only in binary format. Overwrites the file if the file exists. If the file does not exist, creates a new file for writing

w+

Opens a file for both writing and reading. Overwrites the existing file if the file exists. If the file does not exist, creates a new file for reading and writing

wb+

Opens a file for both writing and reading in binary format. Overwrites the existing file if the file exists. If the file does not exist, creates a new file for reading and writing

A

Opens a file for appending. The file pointer is at the end of the file if the file exists. That is, the file is in the append mode. If the file does not exist, it creates a new file for writing

ab

Opens a file for appending in binary format. The file pointer is at the end of the file if the file exists. That is, the file is in the append mode. If the file does not exist, it creates a new file for writing

a+

Opens a file for both appending and reading. The file pointer is at the end of the file if the file exists. The file opens in the append mode. If the file does not exist, it creates a new file for reading and writing

ab+

Opens a file for both appending and reading in binary format. The file pointer is at the end of the file if the file exists. The file opens in the append mode. If the file does not exist, it creates a new file for reading and writing

Once the file is open, we can write the JSON documents to the file by iterating over the list. Iteration means to start at the first element and access the elements in the list one at a time in order (the order they appear in the list). Recall, iteration in Python is very easy. We simply say, “for each item in the list” with the for loop as follows:
for pet in pets:
  // do something with the pet data

To write the JSON document to the file, we use the json.dumps() method, which will produce a JSON formatted string writing that to the file using the file variable and the write() method. Thus, we now see how to build JSON documents from strings and then decode (dump) them to a string.

Once we’ve written data to the file, we then close the file. However, we can use the with clause that will manage the close file operation for us. Without the with clause, you would need to manually close the file with the close() function. You can then reopen it and read data from the file. In this case, we use another special implementation of the for loop. We use the file variable to read all of the lines in the file with the readlines() method and then iterate over them with the following code:
with open("my_data.json", "r") as json_file:
    for pet in json_file.readlines():
      // do something with the pet string
We use the json.loads() method again to read the JSON formatted string as read from the file to convert it to a JSON document, which we add to another list. Now the data has been read back into our program, and we can use it. Finally, we iterate over the new list and print out data from the JSON documents using the key names to retrieve the data we want. Listing 3-7 shows the completed code for this example.
#
# Beginning MicroPython - Chapter 3
#
# Example: Storing and retrieving JSON objects in files
#
# Dr. Charles Bell
#
import json
# Prepare a list of JSON documents for pets by converting JSON
# to a dictionary
pets = []
parsed_json = json.loads('{"name":"Violet", "age": 11, "breed":"dachshund", "type":"dog"}')
pets.append(parsed_json)
parsed_json = json.loads('{"name": "Mister", "age": 16, "breed":"siberian khatru", "type":"cat"}')
pets.append(parsed_json)
parsed_json = json.loads('{"name": "Spot", "age": 13, "breed":"koi", "type":"fish"}')
pets.append(parsed_json)
parsed_json = json.loads('{"name": "Charlie", "age": 11, "breed":"dachshund", "type":"dog"}')
pets.append(parsed_json)
# Now, write these entries to a file. Note: overwrites the file
with open("my_data.json", "w") as json_file:
    for pet in pets:
        json_file.write(json.dumps(pet))
        json_file.write(" ")
# Now, let's read the JSON documents then print the name and age for all of the dogs in the list
my_pets = []
with open("my_data.json", "r") as json_file:
    for pet in json_file.readlines():
        parsed_json = json.loads(pet)
        my_pets.append(parsed_json)
print("Name, Age")
for pet in my_pets:
    if pet['type'] == 'dog':
        print("{0}, {1}".format(pet['name'], pet['age']))
Listing 3-7

Writing and Reading JSON Objects to/from Files

Notice the loop for writing data. We added a second write() method passing in a strange string (it is actually an escape character). The is a special character called the newline character. This forces the JSON formatted strings to be on separate lines in the file and helps with readability.

Tip

For a more in-depth look at how to work with files in Python, see https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files.

So, what does the file look like? The following is a dump of the file using the more utility, which shows the contents of the file. Notice the file contains the JSON formatted strings just like we had in our code:
$ more my_data.json
{"age": 11, "breed": "dachshund", "type": "dog", "name": "Violet"}
{"age": 16, "breed": "siberian khatru", "type": "cat", "name": "Mister"}
{"age": 12, "breed": "koi", "type": "fish", "name": "Spot"}
{"age": 11, "breed": "dachshund", "type": "dog", "name": "Charlie"}

Now, let’s see what happens when we run this script.

Execute the Code

You can save this code in a file named rw_json.py on your PC and then open a terminal (console window) and run the code with the command python ./rw_json.py (or python3 if you have multiple versions of Python installed). The following shows the output:
$ python ./rw_json.py
Name, Age
Violet, 11
Charlie, 11

While the output may not be very impressive, by completing the example, you’ve learned a great deal about working with files and structured data using JSON documents.

Your Challenge

To make this example more of a challenge, you could modify it to include more information about your pets. I suggest you start with a simple text file and type in the JSON formatted strings for your pets. To increase the complexity, try adding information that is pertinent to the type of pet. For example, you could add some keys for one or more pets, other keys for other pets, and so on. Doing so will show one of the powers of JSON documents; collections of JSON documents do not have to have the same format.

Once you have this file, modify the code to read from the file and print out all the information for each pet by printing the key name and value. Hint: You will need to use special code to print out the key name and the value called “pretty printing.” For example, the following code will print out the JSON document in an easily readable format. Notice we use the sort_keys option to print the keys (fields), and we can control the number of spaces to indent:
for pet in my_pets:
    print(json.dumps(pet, sort_keys=True, indent=4))
When run, the output will look like the following:
{
    "age": 11,
    "breed": "dachshund",
    "name": "Violet",
    "type": "dog"
}
...

Example 3: Using Functions

This example demonstrates how to create and use functions. Recall functions are used to help make our code more modular. Functions can also be a key tool in avoiding duplication of code. That is, we can reuse portions of code repeatedly by placing them in a function. Functions are also used to help isolate code for special operations such as mathematical formulae.

The problem we’re exploring in this example is how to create functions to perform calculations. We will also explore a common computer science technique called recursion where a function calls itself repeatedly. I will also show you the same function implemented in an iterative manner (typically using a loop). While some would advise avoiding recursion, recursive functions are a bit shorter to write but can be more difficult to debug if something goes wrong. The best advice I can offer is that almost every recursive function can be written as iterative functions, and novice programmers should stick to iterative solutions until they gain confidence using functions.

Write the Code

This example is designed to calculate a Fibonacci series.5 A Fibonacci series is calculated as the sum of the two preceding values in the series. The series begins with 1 followed by 1 (nothing plus 1), then 1 + 1 = 2, and so on. For this example, we will ask the user for an integer and then calculate the number of values in the Fibonacci series. If the input is 5, the series is 1, 1, 2, 3, 5.

We will create two functions: one to calculate the Fibonacci series using code that iteratively calculates the series and one to calculate the nth Fibonacci number using a recursive function. Let’s look at the iterative function first.

To define a function, we use the syntax def func_name(<parameters>): where we supply a function name and a list of zero or more parameters followed by a colon. These parameters are then usable inside the function. We pass in data to the function using the parameters. The following shows the iterative version of the Fibonacci series code. We name this function fibonacci_iterative:
def fibonacci_iterative(count):
    i = 1
    if count == 0:
        fib = []
    elif count == 1:
        fib = [1]
    elif count == 2:
        fib = [1,1]
    elif count > 2:
        fib = [1,1]
        while i < (count - 1):
            fib.append(fib[i] + fib[i-1])
            i += 1
    return fib

This code simply calculates the first N values in the series and returns them in a list. The parameter count is the number of values in the series. The function begins by checking to see if the trivial values are requested: 0, 1, or 2 whose values are known. If the count value is greater than 2, we begin with the known series [1, 1] and then use a loop to calculate the next value by adding the two previous values together. Take a moment to notice how I use the list index to get the two previous values in the list (i and i-1). We will use this function and the list returned directly in our code to find a specific value in the series and print it.

Now let’s look at the recursive version of the function. The following shows the code. We name this function fibonacci_recursive:
def fibonacci_recursive(number):
    """Calculate the Nth Fibonacci as a recursive function."""
    if number == 0:
        return 0
    if number == 1:
        return 1
    # Call our self counting down.
    value = fibonacci_recursive(number-1) + fibonacci_recursive(number-2)
    return value

In this case, we don’t return the entire series; rather, we return the specific value in the series – the nth value. Like the iterative example, we do the same thing regarding the trivial values returning the number requested. Otherwise, we call the same function again for each number. It may take some time to get your mind around how this works, but it does calculate the nth value.

Now, you may be wondering where you place functions in the code. We need to place them at the top of the code. Python will parse the functions and continue to execute statements following the definitions. Thus, we place our “main” code after our functions.

The main code for this example begins with requesting the nth value for the Fibonacci series and then uses the recursive function first to calculate the value. We then ask the user if they want to see the entire series, and if so, we use the iterative version of the function to get the list and print it out. We print out the nth value and give the option again to see the entire series to show the result is the same using both functions.

Finally, we will place all of the main executable code into a function named main(), which we will call with a special technique that tests to see if the file has been executed. More specifically, if the __name__ parameter is equal to “main”, we call the main() function. This technique allows you to attempt an import statement without executing the main body of code. This enables us to reuse any functions defined in the file. The code for this technique is shown as follows:
def main():
    # Main code goes here
if __name__ == '__main__':
    main()
Listing 3-8 shows the completed code for the example. We will name this code fibonacci.py.
#
# Beginning MicroPython – Chapter 3
#
# Example: Fibonacci series using recursion
#
# Calculate the Fibonacci series based on user input
#
# Dr. Charles Bell
#
# Create a function to calculate Fibonacci series (iterative)
# Returns a list.
def fibonacci_iterative(count):
    """Calculate Fibonacci as an iterative function."""
    i = 1
    if count == 0:
        fib = []
    elif count == 1:
        fib = [1]
    elif count == 2:
        fib = [1,1]
    elif count > 2:
        fib = [1,1]
        while i < (count - 1):
            fib.append(fib[i] + fib[i-1])
            i += 1
    return fib
# Create a function to calculate the nth Fibonacci number (recursive)
# Returns an integer.
def fibonacci_recursive(number):
    """Calculate the Nth Fibonacci as a recursive function."""
    if number == 0:
        return 0
    elif number == 1:
        return 1
    else:
        # Call our self counting down.
        value = fibonacci_recursive(number-1) + fibonacci_recursive(number-2)
        return value
# Main code
def main():
    print("Welcome to my Fibonacci calculator!")
    index = int(input("Please enter the number of integers in the series: "))
    # Recursive example
    print("We calculate the value using a recursive algorithm.")
    nth_fibonacci = fibonacci_recursive(index)
    print("The {0}{1} Fibonacci number is {2}."
          "".format(index, "th" if index > 1 else "st", nth_fibonacci))
    see_series = str(input("Do you want to see all of the values in" " the series? "))
    if see_series in ["Y","y"]:
        series = []
        for j in range(1,index+1):
            series.append(fibonacci_recursive(j))
        print("Series: {0}: ".format(series))
    # Iterative example
    print("We calculate the value using an iterative algorithm.")
    series = fibonacci_iterative(index)
    print("The {0}{1} Fibonacci number is {2}."
          "".format(index, "th" if index > 1 else "st", series[index-1]))
    see_series = str(input("Do you want to see all of the values "
                           "in the series? "))
    if see_series in ["Y","y"]:
        print("Series: {0}: ".format(series))
    print("bye!")
if __name__ == '__main__':
    main()
Listing 3-8

Calculating Fibonacci Series

Take a few moments to read through the code. While the problem being solved is a bit simpler than the previous example, there is a lot more code to read through. When you’re ready, connect your MicroPython board and create the file. You create the file on your PC for this example and name it fibonacci.py. We’ll copy it to our MicroPython board in the next section.

Tip

For a more in-depth look at how to create and use your own functions, see https://docs.python.org/3/tutorial/controlflow.html#defining-functions.

Now, let’s see what happens when we run this script. Recall, we will be running this code on our MicroPython board, so if you’re following along, be sure to set up your board and connect it to your PC.

Execute the Code

Recall from Chapter 3, when we want to move code to our Pico, we need to create the file and then upload it to the Pico and then execute it.

We can do this in Thonny by creating a new file and then saving it. Thonny will ask us where to save the file. Choose the Raspberry Pi Pico option when prompted. If you have already created the file on your PC, you can upload the file to your Pico by right-clicking the file and choosing Upload to /. Once the file is on your Pico, you can execute it.

You will be prompted for the data as shown in the following. Notice we enter an integer for calculating the Nth Fibonacci number, and then we are asked if we want to see all of the values. The first attempt uses the recursive version and the second the iterative:
Welcome to my Fibonacci calculator!
Please enter the number of integers in the series: 13
We calculate the value using a recursive algorithm.
The 13th Fibonacci number is 233.
Do you want to see all of the values in the series? Y
Series: [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233]:
We calculate the value using an iterative algorithm.
The 13th Fibonacci number is 233.
Do you want to see all of the values in the series? Y
Series: [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233]:
bye!
There is another way we can execute the file. Recall, we used the main() function technique to execute the main code if the file is executed. We can also import the file and use any functions in the file. To do this, open the REPL console (or connect to your Pico via Thonny) and enter the following code:
>>> import fibonacci
>>> fibonacci_recursive(7)
13
>>> fibonacci_iterative(7)
[1, 1, 2, 3, 5, 8, 13]

Go ahead and try it yourself.

When you’re done experimenting the example, remember to close the terminal and eject the drive for the flash before you unplug it.

Your Challenge

To make this example a bit more challenging, modify the code to search a Fibonacci series for a specific integer. Ask the user to provide an integer and then determine if the value is a valid Fibonacci value. For example, if the user enters 144, the code should tell the user that value is valid and is the twelfth value in the series. While this challenge will require you rewrite most of the code for the “main” functionality, you must figure out how to use the functions in a new way.

Example 4: Using Classes

This example ramps up the complexity considerably by introducing an object-oriented programming concept: classes. Recall from earlier that classes are another way to modularize our code. Classes are used to model data and behavior on that data. Further, classes are typically placed in their own code module (file) that further modularizes the code. If you need to modify a class, you may need only change the code in the class module.

The problem we’re exploring in this example is how to develop solutions using classes and code modules. We will be creating two files: one for the class and another for the main code. Since this code is more complex, we will execute it first on our PC by creating the files there and then upload them to the Pico and execute them there.

Write the Code

This example is designed to convert Roman numerals to integers. That is, we will enter a value like VIII, which is eight, and expect to see the integer 8. To make things more interesting, we will also take the integer we derive and convert it back to Roman numerals. Roman numerals are formed as a string using the characters I for 1, V for 5, X for 10, L for 50, C for 100, D for 500, and M for 1000. Combinations of other numbers are done by adding the character numerical value together (e.g., 3 = III) or a single, lower character before a character to indicate the representative minus that character (e.g., 4 = IV). The following shows some examples of how this works:
3 = III
15 = XV
12 = XII
24 = XXIV
96 = LXLVI
107 = CVII

This may sound like a lot of extra work but consider this: if we can convert from one format to another, we should be able to convert back without errors. More specifically, we can use the code for one conversion to validate the other. If we get a different value when converting it back, we know we have a problem that needs to be fixed.

To solve the problem, we will place the code for converting Roman numerals into a separate file (code module) and build a class called Roman_Numerals to contain the methods. In this case, the data is a mapping of integers to Roman numerals:
# Private dictionary of roman numerals
__roman_dict = {
    'I': 1,
    'IV': 4,
    'V': 5,
    'IX': 9,
    'X': 10,
    'XL': 40,
    'L': 50,
    'XC': 90,
    'C': 100,
    'CD': 400,
    'D': 500,
    'CM': 900,
    'M': 1000,
}

Notice the two underscores before the name of the dictionary. This is a special notation that marks the dictionary as a private variable in the class. This is a Python aspect for information hiding, which is a recommended technique to use when designing objects; always strive to hide data that is used inside the class.

Notice also that instead of using the basic characters and their values, I used several other values too. I did this to help make the conversion easier (and cheat a bit). In this case, I added the entries that represent the one value previous conversions such as 4 (IV), 9 (IX), etc. This makes the conversion a bit easier (and more accurate).

We will also add two methods: convert_to_int(), which takes a Roman numeral string and converts it to an integer, and convert_to_roman(), which takes an integer and converts it to a Roman numeral. Rather than explain every line of code in the methods, I leave it to you to read the code to see how it works.

Simply, the convert to integer method takes each character and gets its value from the dictionary summing the values. There is a trick there that requires special handling for the lower value characters appearing before higher values (e.g., IX). The convert to Roman method is a bit easier since we simply divide the value by the highest value in the dictionary until we reach zero. Listing 3-9 shows the code for the class module, which is saved in a file named roman_numerals.py.
"""roman_numerals.py"""
#
# Beginning MicroPython – Chapter 3
#
# Example: Roman numerals class
#
# Convert integers to roman numerals
# Convert roman numerals to integers
#
# Dr. Charles Bell
#
class Roman_Numerals:
    """Roman Numerals class"""
    # Private dictionary of roman numerals
    __roman_dict = {
        'I': 1,
        'IV': 4,
        'V': 5,
        'IX': 9,
        'X': 10,
        'XL': 40,
        'L': 50,
        'XC': 90,
        'C': 100,
        'CD': 400,
        'D': 500,
        'CM': 900,
        'M': 1000,
    }
    def convert_to_int(self, roman_num):
        """Convert Roman numeral to integer"""
        value = 0
        for i in range(len(roman_num)):
            if i > 0 and self.__roman_dict[roman_num[i]] >
                self.__roman_dict[roman_num[i - 1]]:
                value += self.__roman_dict[roman_num[i]] - 2 *
                self.__roman_dict[roman_num[i - 1]]
            else:
                value += self.__roman_dict[roman_num[i]]
        return value
    def __find_numeral(self, find_value):
        """Search the dictionary for the Roman numeral"""
        return [item[0] for item in
self.__roman_dict.items() if item[1] == find_value][0]
    def convert_to_roman(self, int_value):
        """Convert integer to Roman numeral"""
        # First, sort the dictionary by value
        roman_values = sorted(list(self.__roman_dict.values()))
        # Prepare the string
        roman_str = ""
        remainder = int_value
        # Loop through the values in reverse
        for i in range(len(roman_values)-1, -1, -1):
            count = int(remainder / roman_values[i])
            if count > 0:
                for j in range(0,count):
                    roman_str += self.__find_numeral(roman_values[i])
                remainder -= count * roman_values[i]
        return roman_str
Listing 3-9

Roman Numeral Class

Notice the function named __find_numeral(). This is a special list operation to search the __roman_dict dictionary to find the Roman number by value. Why is this needed? It is needed because the Pico MicroPython core does not return dictionary values in order. Thus, we either must sort the dictionary (an unnecessary step) or search for the value and return the key or, in this case, the Roman numeral. The following shows a simplified version of this code that works the same way without the special list function:
def __find_numeral_search(self, find_value):
    """Search the dictionary for the Roman numeral using a search"""
    for key in self.__roman_dict.keys():
        if self.__roman_dict[key] == find_value:
            return key
    return None

If you’re following along with the chapter, go ahead and create a file on your PC for this code and name it roman_numerals.py. We’ll copy it to our Pico in the next section.

Now let’s look at the main code. For this, we simply need to import the new class from the code module as follows. This is a slightly different form of the import directive. In this case, we’re telling Python to include the roman_numerals class from the file named Roman_Numerals:
from roman_numerals import Roman_Numerals
Note

If the code module were in a subfolder, say roman, we would have written the import statement as from roman import Roman_Numerals where we list the folders using dot notation instead of slashes.

The rest of the code is straightforward. We first ask the user for a valid Roman numeral string and then convert it to an integer and use that value to convert back to Roman numerals printing the result. So, you see having the class in a separate module has simplified our code, making it shorter and easier to maintain. Listing 3-10 shows the complete main code saved in a file named simply roman.py.
#
# Beginning MicroPython – Chapter 3
#
# Example: Convert roman numerals using a class
#
# Convert integers to roman numerals
# Convert roman numerals to integers
#
# Dr. Charles Bell
#
from roman_numerals import Roman_Numerals
roman_str = input("Enter a valid roman numeral: ")
roman_num = Roman_Numerals()
# Convert to roman numerals
value = roman_num.convert_to_int(roman_str)
print("Convert to integer:        {0} = {1}".format(roman_str, value))
# Convert to integer
new_str = roman_num.convert_to_roman(value)
print("Convert to Roman Numerals: {0} = {1}".format(value, new_str))
print("bye!")
Listing 3-10

Converting Roman Numerals

If you’re following along with the chapter, go ahead and create a file on your PC for this code and name it roman.py. We’ll copy it to our Pico in the next section.

Tip

For a more in-depth look at how to work with classes in Python, see https://docs.python.org/3/tutorial/classes.html.

Now, let’s see what happens when we run this script. Recall, we will be running this code on our MicroPython board, so if you’re following along, be sure to set up your board and connect it to your PC.

Execute the Code

If you haven’t created the files, do that now and save them on your PC. Then, open a terminal and use the python ./roman.py command to execute it, or you can execute it with the Run command in Thonny. When you execute the code, you will be prompted to enter the Roman numeral. Try executing it several times and entering some valid and invalid Roman numerals. You should get results similar to the following:
$ python ./roman.py
Enter a valid roman numeral: VI
Convert to integer:        VI = 6
Convert to Roman Numerals: 6 = VI
bye!
$ python ./roman.py
Enter a valid roman numeral: IIV
Convert to integer:        IIV = 5
Convert to Roman Numerals: 5 = V
bye!
$ python ./roman.py
Enter a valid roman numeral: MMXXI
Convert to integer:        MMXXI = 2021
Convert to Roman Numerals: 2021 = MMXXI
bye!

Notice the second execution. Here, we entered an invalid Roman numeral but got an answer anyway (rather than an error). Clearly, this is an area where we can improve the code.

Now let’s upload the files to the Pico and execute them. Use Thonny to do this. When you run the roman.py file, you should see output similar to the following. Run it a few times until you are convinced it is working correctly:
Enter a valid roman numeral: XIV
Convert to integer:        XIV = 14
Convert to Roman Numerals: 14 = XIV
bye!

Your Challenge

There isn’t much to add for this example to improve it other than perhaps some user friendliness (nicer to use). If you want to improve the code or the class itself, I suggest adding a new method named validate() used to validate a Roman numeral string. This method can take a string and determine if it contains a valid series of characters. Hint: To start, check the string has only the characters in the dictionary.

However, you can use this template to build other classes for converting formats. For example, as an exercise, you could create a new class to convert integers to hexadecimal or even octal. Yes, there are functions that will do this for us, but it can be enlightening and satisfying to build it yourself. Go ahead, give it a go – create a new class to convert integers to other formats. I would suggest doing a hexadecimal to integer function first, and when that is working correctly, create the reciprocal to convert integers to hexadecimal.

A more advanced challenge would be to rewrite the class to accept a string in the constructor (when the class variable is created) and use that string to do the conversions instead of passing the string or integer using the convert_to* methods. For example, the class could have a constructor and private member as follows:
__roman_str = ""
...
def __init__(self, name):
        self.name = name
When you create the instance, you will need to pass the string or else you will get an error that a required parameter is missing.
roman_str = input("Enter a valid roman numeral: ")
roman_num = Roman_Numerals(roman_str)

For More Information

Should you require more in-depth knowledge of Python, there are several excellent books on the topic. I list a few of my favorites in the following. A great resource is the documentation on the Python site: python.org/doc/.
  • Pro Python, Second Edition (Apress 2014) J. Burton Browning , Marty Alchin

  • Learning Python, 5th Edition (O'Reilly Media 2013) Mark Lutz

  • Automate the Boring Stuff with Python: Practical Programming for Total Beginners (No Starch Press 2015), Al Sweigart

Summary

Wow! That was a wild ride, wasn’t it? I hope that this short crash course in Python has explained enough about the sample programs shown so far that you now know how they work. This crash course also forms the basis for understanding the other Python examples in this book.

If you are learning how to work with IoT projects and don’t know how to program with Python, learning Python can be fun given its easy-to-understand syntax. While there are many examples on the Internet you can use, very few are documented in such a way as to provide enough information for someone new to Python to understand or much less get started and deploy the sample! But at least the code is easy to read.

This chapter has provided a crash course in Python that covers the basics of the things you will encounter when examining most of the smaller example projects. We discovered the basic syntax and constructs of a Python application including a walk-through of building a real Python application that blinks an LED. Through that example, we learned how to work with headless applications including how to manage a startup background application.

In the next chapter, we’ll dive deeper into the Pico hardware. We will see more about the special libraries available for use in your projects written for running on the Pico.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.239.11.178