跳到內容

使用 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 處理不完美 → 
華夏公益教科書