跳到内容

使用 Linkbot 学习 Python 3/文件 I/O

来自维基教科书,开放世界中的开放书籍

文件 I/O

[编辑 | 编辑源代码]

这是一个简单的文件 I/O(输入/输出)示例

# 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!")

# Read a file
with open("test.txt", "rt") as in_file:
    text = in_file.read()

print(text)

输出和文件 test.txt 的内容是

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

请注意,它在您运行程序的目录中创建了一个名为 test.txt 的文件。字符串中的 \n 告诉 Python 在该位置放置一个换行符

文件 I/O 的概述是

  • 使用 open 函数获取文件对象
  • 读取或写入文件对象(取决于打开方式)
  • 如果您没有使用 with 打开文件,则需要手动关闭它

第一步是获取文件对象。实现这一点的方法是使用 open 函数。格式为 file_object = open(filename, mode),其中 file_object 是用于放置文件对象的变量,filename 是包含文件名的字符串,mode"rt" 用于以文本形式读取文件,或 "wt" 用于以文本形式写入文件(以及我们在此跳过的其他一些模式)。接下来可以调用文件对象函数。两个最常见的函数是 readwritewrite 函数将一个字符串添加到文件的末尾。read 函数读取文件中下一个内容并将其作为字符串返回。如果没有给出参数,它将返回整个文件(如示例中所示)。

现在这里是我们之前创建的电话号码程序的新版本

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

def add_number(numbers, name, number):
    numbers[name] = number

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

def remove_number(numbers, name):
    if name in numbers:
        del numbers[name]
    else:
        print(name," was not found")

def load_numbers(numbers, filename):
    in_file = open(filename, "rt")
    while True:
        in_line = in_file.readline()
        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()

def print_menu():
    print('1. Print Phone Numbers')
    print('2. Add a Phone Number')
    print('3. Remove a Phone Number')
    print('4. Lookup a Phone Number')
    print('5. Load numbers')
    print('6. Save numbers')
    print('7. Quit')
    print()

phone_list = {}
menu_choice = 0
print_menu()
while True:
    menu_choice = int(input("Type in a number (1-7): "))
    if menu_choice == 1:
        print_numbers(phone_list)
    elif menu_choice == 2:
        print("Add Name and Number")
        name = input("Name: ")
        phone = input("Number: ")
        add_number(phone_list, name, phone)
    elif menu_choice == 3:
        print("Remove Name and Number")
        name = input("Name: ")
        remove_number(phone_list, name)
    elif menu_choice == 4:
        print("Lookup Number")
        name = input("Name: ")
        print(lookup_number(phone_list, name))
    elif menu_choice == 5:
        filename = input("Filename to load: ")
        load_numbers(phone_list, filename)
    elif menu_choice == 6:
        filename = input("Filename to save: ")
        save_numbers(phone_list, filename)
    elif menu_choice == 7:
        break
    else:
        print_menu()

print("Goodbye")

请注意,它现在包括保存和加载文件。以下是我运行该程序两次时的部分输出

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

Type in a number (1-7): 2
Add Name and Number
Name: Jill
Number: 1234
Type in a number (1-7): 2
Add Name and Number
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
2. Add a Phone Number
3. Remove a Phone Number
4. Lookup a Phone Number
5. Load numbers
6. Save numbers
7. Quit

Type in a number (1-7): 5
Filename to load: numbers.txt
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

该程序的新部分是

def load_numbers(numbers, filename):
    in_file = open(filename, "rt")
    while True:
        in_line = in_file.readline()
        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()

首先,我们将看一下程序的保存部分。首先,它使用命令 open(filename, "wt") 创建一个文件对象。接下来,它遍历并使用命令 out_file.write(x + "," + numbers[x] + "\n") 为每个电话号码创建一行。这将写入包含姓名、逗号、号码的一行,并在其后加上一个换行符。

加载部分稍微复杂一些。它首先获取一个文件对象。然后,它使用 while True: 循环,直到遇到 break 语句才会停止循环。接下来,它使用 in_line = in_file.readline() 获取一行。当遇到文件末尾时,readline 函数将返回一个空字符串。if 语句检查这一点,并在发生这种情况时从 while 循环中退出。当然,如果 readline 函数没有在行尾返回换行符,就无法判断空字符串是一个空行还是文件末尾,因此换行符将保留在 readline 返回的内容中。因此,我们必须删除换行符。in_line = in_line[:-1] 行通过删除最后一个字符来做到这一点。接下来,name, number = in_line.split(",") 行在逗号处将该行拆分为一个姓名和一个号码。然后将其添加到 numbers 字典中。

.txt 文件的进阶使用

[编辑 | 编辑源代码]

你可能会对自己说,“好吧,我知道如何读取和写入文本文件,但如果我想打印文件而不打开另一个程序呢?”

有几种不同的方法可以实现这一点。最简单的方法确实会打开另一个程序,但所有操作都在 Python 代码中处理,不需要用户指定要打印的文件。此方法涉及调用另一个程序的子进程。

还记得我们在上面程序中写入输出的文件吗?让我们使用那个文件。请记住,为了防止出现一些错误,该程序使用了下一章中的概念。请随时在阅读完下一章后再查看此示例。

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
        subprocess.call(['notepad','/p','numbers.txt'])
    except WindowsError:
        print("The called subprocess does not exist, or cannot be called.")

main()

subprocess.call 接受三个参数。在本示例的上下文中,第一个参数应该是您要从中调用打印子进程的程序名称。第二个参数应该是该程序中的特定子进程。简单来说,请理解,在本程序中,'/p' 是用于通过指定应用程序访问打印机的子进程。最后一个参数应该是您要发送到打印子进程的文件名称。在本例中,它与本章前面使用的相同文件。

存储和加载保存在文本文件中的音乐

[编辑 | 编辑源代码]

本节将演示如何将简单的旋律存储在文件中。然后,我们可以编写一个程序读取该文件并在 Linkbot 上播放旋律。

一点音乐理论

[编辑 | 编辑源代码]

为了充分理解示例程序,我们需要了解一些音乐理论知识。如果您不关心音乐理论,只是想让 Linkbot 播放音乐,可以跳过下一节。

什么是音乐?音乐是一系列按特定顺序演奏的音符。有时,会同时演奏多个音符。每个音符都以一定的时长演奏。音符本身是我们可以听到的空气中的振动。每个音符都有一个特定的振动频率;当频率发生变化时,我们感知到的音符音高也会发生变化。

每个音符都有一个名称。如果您知道它的名称,您可以在钢琴键盘上找到它,反之亦然。您可能听说过“中央C”或“Do-Re-Mi”。这两种方法都是指音符。如果您熟悉钢琴键盘,您会知道“C”音符在键盘上出现不止一次。当您演奏 C 音符时,您会注意到它们听起来并不相同,但它们都被称为“C”。事实证明,有 12 个音调不断重复。从 C 开始,它们是

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

“#”符号读作“升”,所以 C# 读作“C升”。“升”表示音调比正常音符高一个音阶。例如,“A升”比“A”高一个音阶。您可能也熟悉另一个看起来像小写 b 的符号。该符号读作“降”,所以 “Bb” 读作“B降”。它与升具有相反的含义,表示音调比降音符低一个音阶。这意味着音符 “G升” 实际上与 “Ab” 是同一个音符。现在,为了避免混淆,我们只使用升。在钢琴键盘上,所有升/降音符都是黑键,其余音符都是白键。

在钢琴键盘上,这些音符不断重复,因此我们必须设计一种方法来明确地指代我们指代的是哪个 A 或 B 或 C。为此,我们引入了八度音阶的概念。钢琴可以演奏的最低音符称为 “A0”,其中 0 是八度音阶编号。从钢琴键盘的左侧到右侧,每当您遇到 C 音符时,八度音阶编号就会增加。因此,钢琴键盘上从左到右的前几个白键是

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

现在,我们有了一种方法可以使用诸如 “F#5” 之类的字符串来明确地指定键盘上的哪个键。

我们还有一个方程式,我们可以根据音符与我们的 “A0” 音符相差多少个音阶来计算音符的频率。该方程式是

例如,G0 比 A0 低 2 个音阶,而 B0 比 A0 高 2 个音阶。对于同一八度的音符,我们可以简单地数一下黑白键的数量来找到与 A 的距离,但不同八度呢?例如,A0 与 B4 之间有多少个键?

首先,让我们考虑一下 A4 与 A0 之间有多少个音符。当我们数键时,每个八度有 12 个音符。由于 A4 比 0 高 4 个八度(4-0 = 4),所以 A4 比 A0 高 4*12=48 个键。

那么 A0 和 B4 呢?A0 比 B0 低 2 个键,而 B0 比 B4 高 4*12 个键,所以总的来说,A0 比 B4 低 2+4*12 = 50 个键。现在,我们可以写一个方程来准确计算出任何音符与 A0 之间的键数。让我们使用变量 来表示我们想要的音符名称,并使用 来表示音符的八度。那么,

从文件读取和播放旋律

[edit | edit source]

首先,让我们编写一个函数,该函数接收一个描述音符的字符串(例如 "A4")并为该音符提供频率。以下是我们的策略

  • 我们接收一个描述音符的字符串。第一个字符是音符的字母名称。我们计算该字母名称与 "A" 音符的偏移量。例如,"B" 的偏移量为 +2,而 "E" 的偏移量为 -5。我们可以通过简单地在键盘上数键来找到这些偏移量。
  • 字符串中的下一个字符可能是 "#" 锐调或 "b" 降调。如果是锐调,则将偏移量增加 1。如果是降调,则将偏移量减少 1。如果两者都不是,则不做任何操作。
  • 最后一个字符是八度数。我们将此数字乘以 12 并将其添加到我们的偏移量中。
  • 结果是该音符与 A0 之间的键数。现在我们可以使用方程 来根据该音符与 A0 之间的键数偏移量来查找频率。

接下来,让我们谈谈我们的文件格式。由于 Linkbot 只能一次播放一个音符,因此文件应指定 Linkbot 要播放的单个音符。此外,文件应指定播放每个音符的秒数。我们选择让我们的文件每行包含一个音符名称(例如 "C4" 或 "Bb3"),以及以秒为单位的音符持续时间。

以下是我们为您创建的文件

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

继续并将文本复制粘贴到名为 "fur_elise.txt" 的文件中。让我们编写我们的函数和程序,这些函数和程序将读取此文件并播放一首曲子。

import time                                                                      
import barobo                                                                    
dongle = barobo.Dongle()                                                         
dongle.connect()                                                                 
myLinkbot = dongle.getLinkbot()                                                  
                                                                                 
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                                                                                         
    myLinkbot.setBuzzerFrequency(noteToFreq(data[0]))                                                            
    time.sleep( float(data[1]) )                                                                                 
    myLinkbot.setBuzzerFrequency(0)
  1. note[0].upper() 获取第一个字符并将其大写。我们希望将所有传入内容都大写,以防用户使用了小写音符名称,例如 "e4"。由于我们使用大写音符名称编写了字典,因此我们需要确保传入的音符名称也是大写的,以便能够与我们字典中的名称匹配。
  2. " -1 " 索引表示列表的最后一个项目。由于我们列表中的所有项目都是字符串,因此我们需要使用 int 将其转换为整数。
  3. 此循环逐行遍历音乐文件中的每一行。每次循环都会将该行的文本存储在 line 变量中。
  4. split() 函数根据空格拆分文本行。例如,"Hello there".split() 将变为列表 ["Hello", "there"] 。由于我们文本文件中的每一行都有 2 个 "词",因此第一部分(音符名称)将存储在我们的变量 data[0] 中,第二部分(音符持续时间)将存储在 data[1] 中。

练习

[edit | edit source]

现在修改 字典 部分中的成绩程序,使其使用文件 I/O 来记录学生信息。

解决方案

现在修改 字典 部分中的成绩程序,使其使用文件 I/O 来记录学生信息。

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

def load_grades(gradesfile):
    inputfile = open(gradesfile, "r")
    grades = [ ]
    while True:
        student_and_grade = inputfile.readline()
        student_and_grade = student_and_grade[:-1]
        if not student_and_grade:
            break
        else:
            studentname, studentgrades = student_and_grade.split(",")
            studentgrades = studentgrades.split(" ")
            students[studentname] = studentgrades
    inputfile.close()
    print("Grades loaded.")

def save_grades(gradesfile):
    outputfile = open(gradesfile, "w")
    for k, v in students.values():
        outputfile.write(k + ",")
        for x in v:
            outputfile.write(x + " ")
        outputfile.write("\n")
    outputfile.close()
    print("Grades saved.")

def print_menu():
    print("1. Add student")
    print("2. Remove student")
    print("3. Load grades")
    print("4. Record grade")
    print("5. Print grades")
    print("6. Save grades")
    print("7. Print Menu")
    print("9. Quit")

def print_all_grades():
    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=' ')
            grades = students[x]
            print_grades(grades)
    else:
        print("There are no grades to print.")

def print_grades(grades):
    for x in grades:
        print(x, '\t', end=' ')
    print()

print_menu()
menu_choice = 0
while menu_choice != 9:
    print()
    menu_choice = int(input("Menu Choice: "))
    if menu_choice == 1:
        name = input("Student to add: ")
        students[name] = [0] * len(assignments)
    elif menu_choice == 2:
        name = input("Student to remove: ")
        if name in students:
            del students[name]
        else:
            print("Student:", name, "not found")
    elif menu_choice == 3:
        gradesfile = input("Load grades from which file? ")
        load_grades(gradesfile)
    elif menu_choice == 4:
        print("Record Grade")
        name = input("Student: ")
        if name in students:
            grades = students[name]
            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()
            print_grades(grades)
            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
                    grades[which] = grade
                elif which != -1:
                    print("Invalid Grade Number")
        else:
            print("Student not found")
    elif menu_choice == 5:
        print_all_grades()
    elif menu_choice == 6:
        gradesfile = input("Save grades to which file? ")
        save_grades(gradesfile)
    elif menu_choice != 9:
        print_menu()


编写一个程序,该程序读取一个包含电机角度的文本文件,例如

90 45 20
180 22 -180
5 32 0

每三个数字代表三个电机角度。编写一个程序,该程序使用 moveTo() 函数将 Linkbot 的关节移动到数据文件中的每一行的这些位置。

使用 Linkbot 学习 Python 3
 ← 字符串的复仇 文件 I/O 处理不完美 → 
华夏公益教科书