# Learning Python 3 with the Linkbot/File IO

### File I/O

Here is a simple example of file I/O (input/output):

# Write a file
with open("test.txt", "wt") as out_file:
out_file.write("This Text is going to out file\nLook at it and see!")

with open("test.txt", "rt") as in_file:

print(text)


The output and the contents of the file test.txt are:

This Text is going to out file
Look at it and see!


Notice that it wrote a file called test.txt in the directory that you ran the program from. The \n in the string tells Python to put a newline where it is.

A overview of file I/O is:

• Get a file object with the open function
• Read or write to the file object (depending on how it was opened)
• If you did not use with to open the file, you'd have to close it manually

The first step is to get a file object. The way to do this is to use the open function. The format is file_object = open(filename, mode) where file_object is the variable to put the file object, filename is a string with the filename, and mode is "rt" to read a file as text or "wt" to write a file as text (and a few others we will skip here). Next the file objects functions can be called. The two most common functions are read and write. The write function adds a string to the end of the file. The read function reads the next thing in the file and returns it as a string. If no argument is given it will return the whole file (as done in the example).

Now here is a new version of the phone numbers program that we made earlier:

def print_numbers(numbers):
print("Telephone Numbers:")
for k, v in numbers.items():
print("Name:", k, "\tNumber:", v)
print()

numbers[name] = number

def lookup_number(numbers, name):
if name in numbers:
return "The number is " + numbers[name]
else:

def remove_number(numbers, name):
if name in numbers:
del numbers[name]
else:

in_file = open(filename, "rt")
while True:
if not in_line:
break
in_line = in_line[:-1]
name, number = in_line.split(",")
numbers[name] = number
in_file.close()

def save_numbers(numbers, filename):
out_file = open(filename, "wt")
for k, v in numbers.items():
out_file.write(k + "," + v + "\n")
out_file.close()

print('1. Print Phone Numbers')
print('3. Remove a Phone Number')
print('4. Lookup a Phone Number')
print('6. Save numbers')
print('7. Quit')
print()

phone_list = {}
while True:
menu_choice = int(input("Type in a number (1-7): "))
print_numbers(phone_list)
name = input("Name: ")
phone = input("Number: ")
print("Remove Name and Number")
name = input("Name: ")
remove_number(phone_list, name)
print("Lookup Number")
name = input("Name: ")
print(lookup_number(phone_list, name))
filename = input("Filename to load: ")
filename = input("Filename to save: ")
save_numbers(phone_list, filename)
break
else:

print("Goodbye")


Notice that it now includes saving and loading files. Here is some output of my running it twice:

1. Print Phone Numbers
3. Remove a Phone Number
4. Lookup a Phone Number
6. Save numbers
7. Quit

Type in a number (1-7): 2
Name: Jill
Number: 1234
Type in a number (1-7): 2
Name: Fred
Number: 4321
Type in a number (1-7): 1
Telephone Numbers:
Name: Jill     Number: 1234
Name: Fred     Number: 4321

Type in a number (1-7): 6
Filename to save: numbers.txt
Type in a number (1-7): 7
Goodbye

1. Print Phone Numbers
3. Remove a Phone Number
4. Lookup a Phone Number
6. Save numbers
7. Quit

Type in a number (1-7): 5
Type in a number (1-7): 1
Telephone Numbers:
Name: Jill     Number: 1234
Name: Fred     Number: 4321

Type in a number (1-7): 7
Goodbye


The new portions of this program are:

def load_numbers(numbers, filename):
in_file = open(filename, "rt")
while True:
if not in_line:
break
in_line = in_line[:-1]
name, number = in_line.split(",")
numbers[name] = number
in_file.close()

def save_numbers(numbers, filename):
out_file = open(filename, "wt")
for k, v in numbers.values():
out_file.write(k + "," + v + "\n")
out_file.close()


First we will look at the save portion of the program. First it creates a file object with the command open(filename, "wt"). Next it goes through and creates a line for each of the phone numbers with the command out_file.write(x + "," + numbers[x] + "\n"). This writes out a line that contains the name, a comma, the number and follows it by a newline.

The loading portion is a little more complicated. It starts by getting a file object. Then it uses a while True: loop to keep looping until a break statement is encountered. Next it gets a line with the line in_line = in_file.readline(). The readline function will return an empty string when the end of the file is reached. The if statement checks for this and breaks out of the while loop when that happens. Of course if the readline function did not return the newline at the end of the line there would be no way to tell if an empty string was an empty line or the end of the file so the newline is left in what readline returns. Hence we have to get rid of the newline. The line in_line = in_line[:-1] does this for us by dropping the last character. Next the line name, number = in_line.split(",") splits the line at the comma into a name and a number. This is then added to the numbers dictionary.

### Advanced use of .txt files

You might be saying to yourself, "Well I know how to read and write to a textfile, but what if I want to print the file without opening out another program?"

There are a few different ways to accomplish this. The easiest way does open another program, but everything is taken care of in the Python code, and doesn't require the user to specify a file to be printed. This method involves invoking the subprocess of another program.

Remember the file we wrote output to in the above program? Let's use that file. Keep in mind, in order to prevent some errors, this program uses concepts from the Next chapter. Please feel free to revisit this example after the next chapter.

import subprocess
def main():
try:
print("This small program invokes the print function in the Notepad application")
#Lets print the file we created in the program above
except WindowsError:
print("The called subprocess does not exist, or cannot be called.")

main()


The subprocess.call takes three arguments. The first argument in the context of this example, should be the name of the program which you would like to invoke the printing subprocess from. The second argument should be the specific subprocess within that program. For simplicity, just understand that in this program, '/p' is the subprocess used to access your printer through the specified application. The last argument should be the name of the file you want to send to the printing subprocess. In this case, it is the same file used earlier in this chapter.

This section will demonstrate how we can store simple melodies in a file. We can then write a program that reads the file and plays the melody on a Linkbot.

#### A little music theory

To fully understand the sample program, we will need a little knowledge about music theory. If you don't care about music theory and you just want to get the Linkbot to play music, you can skip to the next section.

What is music? Music is a series of notes played in a certain order. Sometimes, more than one note is played together. Each note is played for a certain duration. Notes themselves are vibrations in the air that we can hear. Each note has a certain frequency of vibration; when the frequency changes, the perceived pitch of the note changes too.

Each note has a name. If you know it's name, you can find it on a piano keyboard, and vice versa. You may have heard the term "Middle-C", or the phrase "Do-Re-Mi". These are both ways to refer to notes. If you are familiar with a piano keyboard, you will know that the "C" note appears more than once on the keyboard. When you play the C notes, you'll notice that they don't sound the same, but they are all called "C". It turns out that there are 12 tones that repeat over and over again. Starting at C, they are:

• C
• C# (Db)
• D
• D# (Eb)
• E
• F
• F# (Gb)
• G
• G# (Ab)
• A
• A# (Bb)
• B

The "#" symbol is pronounced "sharp", so C# is pronounced "See-Sharp". The "sharp" indicates that the tone is one step higher than the normal note. For instance, "A#" is one step higher than "A". You might also be familiar with another symbol that looks like a lowercase b. That symbol is pronounced "flat", so "Bb" is pronounced "B-flat". It has the opposite meaning of sharp, indicating that the note is one step lower than the un-flatted note. This means that the note "G#" is actually the same note as "Ab". For now, we will only use sharps to avoid confusion. On a piano keyboard, all of the sharp/flat notes are the black keys and the rest of the notes are the white keys.

On a piano keyboard, these notes repeat over and over again, so we must devise a way to specifically refer which A or B or C we are referring to. To do this, we introduce the concept of an octave number. The lowest note a piano can play is called "A0", where 0 is the octave number. Going from left to right on a piano keyboard, the octave number increases every time you encounter the C note. Thus, the first several white keys on a piano keyboard from left to right are:

• A0
• B0
• C1
• D1
• E1
• F1
• G1
• A1
• B1
• C2
• etc...

Now, we have a way to specify exactly what key on a keyboard we are referring to using a string such as "F#5".

We also have an equation where we can calculate the frequency of a note depending on how many steps away it is from our "A0" note. The equation is:

${\displaystyle \mathrm {frequency} =27.5*2^{\mathrm {steps} /12}}$

For instance, G0 is -2 steps away from A0, and B0 is 2 steps away from A0. For notes in the same octave, we can simply count the number of black and white keys to find the distance away from A, but how about different octaves? For instance, how many keys away is A0 from B4?

First, lets consider how many notes away A4 is from A0. When we count the keys, each octave is 12 notes. Since A4 is 4 octave away from 0 (4-0 = 4), A4 is 4*12=48 keys away from A0.

How about A0 and B4? A0 is 2 keys away from B0, and B0 is 4*12 keys away from B4, so in total, A0 is 2+4*12 = 50 keys away from B4. Now, we can write an equation to figure out exactly how many keys away any note is from A0. Lets use the variable ${\displaystyle n}$  for the note name we want, and ${\displaystyle v}$  for the octave of the note. Then,

${\displaystyle \mathrm {offset} (n,v)=n-\mathrm {(} A0)+12*v}$

#### Reading and playing a melody from a file

First, let us write a function that takes a string describing a note such as "A4" and gives us a frequency for that note. Here is our strategy:

• We receive a string describing the note. The first character is the letter name of the note. We calculate the offset of that letter name from the "A" note. For instance, "B" would have an offset of +2 and "E" would have an offset of -5. We find these offsets simply by counting the keys on a keyboard.
• The next character in the string might be a "#" sharp or a "b" flat. If it is sharp, increase the offset by one. If it is flat, decrease it by one. If it is neither, don't do anything.
• The final character is the octave number. We multiply this number by 12 and add it to our offset.
• The result is the number of keys away the note is away from A0. We can now use the equation ${\displaystyle f=27.5*2^{\mathrm {offset} /12}}$  to find the frequency based on the key offset from A0.

Next, lets talk about our file format. Since the Linkbot can only play one note at a time, the file should specify single notes for the Linkbot to play. Also, the file should specify how many seconds to play each note. We choose to have our file such that each line contains a note name such as "C4" or "Bb3", along with a note duration in seconds.

Here is a file that we have created for you:

fur_elise.txt

E5  0.125
D#5 0.125
E5  0.125
D#5 0.125
E5  0.125
B4  0.125
D5  0.125
C5  0.125
A4  0.375
C4  0.125
E4  0.125
A4  0.125
B4  0.375
G#3 0.125
G#4 0.125
B4  0.125
C5  0.375
E4  0.125
E5  0.125
D#5 0.125
E5  0.125
D#5 0.125
E5  0.125
B4  0.125
D5  0.125
C5  0.125
A4  0.375
C4  0.125
E4  0.125
A4  0.125
B4  0.375
E4  0.125
C5  0.125
B4  0.125
A4  0.375


Go ahead and copy-paste the text into a file called "fur_elise.txt". Lets write our function and program that will read this file and play a tune.

import time
import barobo
dongle = barobo.Dongle()
dongle.connect()

def noteToFreq(note):
# First, we need to figure out where the note is relative to 'A'.
# For instance, 'B' is 2 half-steps above A, 'C' is 9 half-steps
# below 'A'. Lets create a dictionary called "note-offset" and store
# all of our offsets from A in the dictionary.
noteOffsets = {
'C' : -9,
'D' : -7,
'E' : -5,
'F' : -4,
'G' : -2,
'A' : 0,
'B' : 2 }
# Find our offset
offset = noteOffsets[ note[0].upper() ]  # 1
# See if there is a sharp or flat
if note[1] == '#':
offset += 1
elif note[1] == 'b':
offset -= 1
# Calculate the offset based on the octave
octave = int(note[-1])  # 2
offset += (octave)*12

# Calculate the note frequency
freq = 2**((offset)/12)*27.5
return freq

musicFile = open('fur_elise.txt', 'r')
for line in musicFile:  # 3
data = line.split()  # 4
time.sleep( float(data[1]) )

1. note[0].upper() takes the first character and uppercases it. We want to uppercase anything that comes in just in case the user used a lowercase note name, like "e4". Since we wrote our dictionary with uppercase note names, we need to make sure the incoming note names are upper-case too so that it can be matched with the ones in our dictionary.
2. The "-1" index means the last item of a list. Since all of the items in our list are strings, we need to convert it to an integer with int.
3. This loop goes through every single line in the music file, line by line. Each time, it stores the text of the line in the line variable.
4. The split() function splits lines of text based on whitespace. For instance, "Hello there".split() becomes the list  ["Hello", "there"] . Since each line in our text file has 2 "words", the first part (the note name) is stored into our variable data[0], and the second part (the note duration) is stored into data[1].

### Exercises

Now modify the grades program from section Dictionaries so that is uses file I/O to keep a record of the students.

Solution

Now modify the grades program from section Dictionaries so that is uses file I/O to keep a record of the students.

assignments = ['hw ch 1', 'hw ch 2', 'quiz   ', 'hw ch 3', 'test']
students = { }

while True:
break
else:
inputfile.close()

for k, v in students.values():
outputfile.write(k + ",")
for x in v:
outputfile.write(x + " ")
outputfile.write("\n")
outputfile.close()

print("2. Remove student")
print("9. Quit")

if students:
keys = sorted(students.keys())
print('\t', end=' ')
for x in assignments:
print(x, '\t', end=' ')
print()
for x in keys:
print(x, '\t', end=' ')
else:
print("There are no grades to print.")

print(x, '\t', end=' ')
print()

print()
name = input("Student to add: ")
students[name] = [0] * len(assignments)
name = input("Student to remove: ")
if name in students:
del students[name]
else:
name = input("Student: ")
if name in students:
print("Type in the number of the grade to record")
print("Type a 0 (zero) to exit")
for i,x in enumerate(assignments):
print(i + 1, x, '\t', end=' ')
print()
which = 1234
while which != -1:
which = int(input("Change which Grade: "))
which -= 1
if 0 <= which < len(grades):
grade = input("Grade: ") # Change from float(input()) to input() to avoid an error when saving
elif which != -1:
else:

90 45 20

Each triplet of numbers represents three motor angles. Write a program that moves the Linkbot's joints to those positions with the moveTo() function for each line in the data file.