10分鍾教你用Python打造學生成績管理系統


前言

大家好,這學期上了Python這門課,然后結課的時候老師要求做一個這樣的學生管理系統。自己按照老師的要求寫了一下,今天就把這個小程序分享出來吧供Python新手小朋友學習

欲下載本文相關的完整代碼及算例,請關注公眾號【程序猿聲】,后台回復【Python成績管理】不包括【】即可

1 總體構思

其實類似這類信息管理系統之類的程序,核心還是和數據打交道吧,包括增刪查改,讀取、展示、保存等。

在數據結構上,我依然用了老師給定的數據結構,即:

score1 = { "姓名":"張三豐", 
         "學號":"U19990001", 
         "作業" : [80, 64, 67, 20], 
         "測驗" : [75, 75], 
         "實驗" : [78, 57] ,
         "分數" : 0
       } 

沒有增加新的字段比如排名之類的。這樣做的主要是考慮到排名、平均成績等均可以由上述結構中的信息計算出來,而且也可以避免因為一個某個成績變動,導致一系列的數據需要重新計算。畢竟,數據存儲得越多,維護起來的難度就越大,特別是一些關聯密切的數據更是如此。

在存儲在結構上,我采用了Python中常用的列表作為此程序的“數據庫”,因為列表操作起來還是非常方便的。此外,因為這里涉及到一個排名的問題,所以我制定了一個原則:在列表中的所有數據實體都是按照成績高低進行排序的,即整個存儲信息的列表由始至終都是有序的。這樣就解決了排名的問題,至於如何實現的,后續我會進行闡述。

運行環境:采用的是Windows 10 x64位操作系統+anaconda(Python3.7)+Spyder,默認情況下即可運行,不需要安裝其他庫。

2 程序說明

這一節我將介紹一下該程序相應的功能以及相應的代碼實現。在此之前先介紹我自己設定的一些規則:

  • 計算成績時取小數點后三位。
  • 排名根據[分數、作業平均、測驗平均、實驗平均]的優先級比較。不存在排名相同的情況。如果這4項指標都相同,emmm應該不會有這么巧的事情。
  • 文件保存和讀取時,采取CSV格式的數據文件。文件頭遵循:['序號','姓名','學號','分數','排名','作業1','作業2','作業3','作業4', '測驗1', '測驗2', '實驗1', '實驗2']這種格式。

2.0 主界面

整個程序的主界面如下,哈哈,擁有直男審美的我實在是沒有辦法進一步美化了。

在整個程序的交互中,為了更好提高提示信息的辨識度,我規定了幾種顏色:

  • 藍色提示內容表示需要用戶輸入相關信息。
  • 紅色表示系統執行指令的結果,比如成功,失敗等等。
  • 正常的黑色表示系統菜單顯示啊,查詢結果的輸出等。

2.1 添加學生信息

在添加學生信息中,在實現了手動添加信息的基礎上,我又增加了從文件中導入信息的功能。不過在添加信息這塊,我做了一個約束:添加學生信息時,如果系統中已經存在該學生的學號,則不能重復添加。兩種方式都遵循該原則,以保證學號的唯一性。

手動添加學生信息
從文件中添加學生信息

在添加學生信息時,因為前面說了列表里面的數據需要保持有序性,所以我采取了插入排序的方式進行添加,核心的代碼如下:

# 根據優先級[分數、作業平均、測驗平均、實驗平均]比較s1是否優於s2
def cmp_student(s1, s2):
    if s1["分數"] != s2["分數"]:
        return s1["分數"] > s2["分數"]
    else:
        if np.mean(s1["作業"]) != np.mean(s2["作業"]):
            return np.mean(s1["作業"]) > np.mean(s2["作業"])
        else:
            if np.mean(s1["測驗"]) != np.mean(s2["測驗"]):
                return np.mean(s1["測驗"]) > np.mean(s2["測驗"])
            else:
                return np.mean(s1["實驗"]) > np.mean(s2["實驗"])

# 根據分數大小,將學生信息插入到列表中,插入排序
def add_to_list(stu, stu_list):
    if len(stu_list):
        if cmp_student(stu, stu_list[0]): # 比第一名還優秀
            stu_list.insert(0,stu)
        elif not cmp_student(stu, stu_list[-1]): # 比最后一名還差
            stu_list.append(stu)
        else:
            for i in range(len(stu_list)-1):
                if (not cmp_student(stu, stu_list[i])) and (cmp_student(stu, stu_list[i+1])):
                    stu_list.insert(i+1, stu)
                    return
    else:
        stu_list.append(stu)

手動添加時,逐個輸入學生的信息,最后按照分數插入到相應的位置,注意的是,需要保證在輸入成績時確保獲取的是數字,否則提示錯誤需要用戶重新輸入:

# 輸入一個數字
def input_number(information):
    while True:
        try:
            print("\033[34m",end='')
            number = input(information)
            print("\033[0m",end='')
            if type(eval(number)) == float or type(eval(number)) == int:
                return float(number)
        except :
            print('\033[1;31m',end='')
            print("輸入有誤,請輸入一個數字!")
            print('\033[0m',end='')

注:類似print("\033[34m",end='')這類語句是控制輸出的字體顏色的。下同

從文件中添加時,系統提供了默認文件的選項,直接回車則默認從data_file目錄下的學生成績信息.csv文件導入,因為有些用戶是懶得輸入文件名的。需要注意的是,導入的文件中,允許成績選項缺失,如果缺失了,則利用其它成績重新計算得出。但其它必要信息不能缺失:

# 從文件添加學生信息
# 需要遵循格式:['序號','姓名','學號','分數','排名','作業1','作業2','作業3','作業4', '測驗1', '測驗2', '實驗1', '實驗2']
def add_from_file(stu_list):
    print("\033[34m",end='')
    fn = input("請輸入文件路徑(例如: C:/a.csv, 直接回車則默認為[./data_file/學生成績信息.csv]) >> ")
    print("\033[0m",end='')
    file_path = './data_file/'+'學生成績信息.csv' # 默認選項
    if fn != '':
        file_path = fn
    n = 0
    n_du = 0
    with open(file_path) as csvfile:
        csv_reader = csv.reader(csvfile)  # 使用csv.reader讀取csvfile中的文件
        next(csv_reader)  # 跳過文件頭
        for row in csv_reader:  # 讀取數據
            if find_student_uid(row[2], stu_list) != -INF: # 如果存在學號相同,則不添加
                n_du = n_du + 1
                continue
            work       = [float(x) for x in row[5:9]] #轉化作業成績
            test       = [float(x) for x in row[9:11]] #轉化測驗成績
            experiment = [float(x) for x in row[11:]] #轉化實驗成績
            score = 0
            if row[3] == '':
                score = calc_score(work, test, experiment) # 考慮到成績位置為空的情況,重新計算成績。
            else:
                score = float(row[3])
            stu_info = {'姓名':row[1], '學號':row[2], '作業':work,
                    '測驗':test, '實驗': experiment, '分數':score}
            add_to_list(stu_info,stu_list)   #將字典數據添加到列表中,插入排序。
            n = n + 1
    print('\033[1;31m')  
    print("從文件["+file_path+"]添加信息成功!共添加 "+str(n)+" 條信息,跳過 "+str(n_du)+" 條重復信息!")
    print('\033[0m')  

    return stu_list

2.2 修改學生信息

這一塊比較簡單,找到學生信息后,輸入相應信息然后修改。大部分都是提示輸入的語句。

修改學生信息

不過需要注意的是,修改了相應的作業、實驗等成績后,需要更新學生的分數,同時重新計算學生的排名,將該生挪到列表的相應位置上。具體做法在我的代碼實現中比較簡單,先將該生從列表中移除,重新計算分數后再按照插入排序的思路放進列表即可。這樣速度可能會快一些。因為變動信息的只有一個學生,如果再次對整個列表進行排序可能會造成比較大的開銷。

2.3 刪除學生信息

這一塊也相對來說比較簡單,找到學生后,如果確認刪除,則直接刪除該學生即可。刪除后其他學生的次序依然是有序的,無需再做調整。

2.4 查找學生信息

查找學生相關信息是通過學號遍歷列表進行搜尋,找到后輸出學生的相關信息。

找到學生的信息輸出

不過我在此基礎上,對學生成績進行了簡單的統計,並通過圖表的方式進行呈現。能夠讓老師或學生更直觀地看到各科成績的詳細內容,找出自己的優勢與不足,便於下次努力改進。(不過這里因為想把兩個圖拼在一個圖上,因為不熟悉操作做了好久~

bar1_colors = ['#7199cf','#4fc4aa','#e1a7a2']
labels = np.array(['作業1','作業2','作業3','作業4','測驗1','測驗2','實驗1','實驗2'])
name=['作業','測驗','實驗']
# 統計學生成績等信息
def statistics_student(stu):
    #=======自己設置開始============
    #標簽
    #數據個數
    dataLenth = len(stu["作業"])+len(stu["測驗"])+len(stu["實驗"])
    #數據
    all_scores = stu["作業"] + stu["測驗"] + stu["實驗"]
    data = np.array(all_scores)
    average_score=[np.mean(stu["作業"]),np.mean(stu["測驗"]),np.mean(stu["實驗"])]
    
    #========自己設置結束============
    
    angles = np.linspace(0, 2*np.pi, dataLenth, endpoint=False)
    data = np.concatenate((data, [data[0]])) # 閉合 # #將數據結合起來
    angles = np.concatenate((angles, [angles[0]])) # 閉合
    
    fig = plt.figure(figsize=(8, 4.2), dpi=80)
    ax = fig.add_subplot(121, polar=True)# polar參數!!121代表總行數總列數位置
    ax.plot(angles, data, 'bo-', linewidth=1)# 畫線四個參數為x,y,標記和顏色,閑的寬度
    ax.fill(angles, data, facecolor='r', alpha=0.1)# 填充顏色和透明度
    ax.set_thetagrids(angles * 180/np.pi, labels, fontproperties='SimHei')
    ax.set_title("{} 詳細成績雷達圖".format(stu["姓名"]),fontproperties='SimHei',weight='bold', size='medium', position=(0.5, 1.11),
                     horizontalalignment='center', verticalalignment='center')
    ax.set_rlim(0,100)
    ax.grid(True)
    xticks = np.arange(len(average_score))  #生成x軸每個元素的位置
    ax=fig.add_subplot(133)
    ax.set_xticklabels(name, fontproperties='SimHei')
    ax.set_xticks(xticks)  #設置x軸上每個標簽的具體位置
    ax.set_ylim([0, 100]) # 設置y軸范圍
    ax.bar(xticks,average_score,color=bar1_colors)
    ax.set_title("{} 平均成績柱狀圖".format(stu["姓名"]),fontproperties='SimHei')
    plt.show()

2.5 打印全體學生成績信息

這一個功能實現也蠻簡單,遍歷學生列表,然后調用打印函數逐個進行打印輸出即可,這里輸出單個學生信息的時候就沒有輸出統計圖的信息了。主要是考慮到人數過多時,輸出圖的話,可能會導致速度過慢,影響體驗。輸出完成后會簡單統計一下一共有幾個人。

打印全體學生的信息

2.6 課程成績統計

在統計成績這個模塊中,由於數據在列表中已經是有序的了,所以最高分最低分,中位數的獲取都比較容易。而平均分也可以很快得出。(其實我覺得,程序的整體結構和思路做好以后,功能模塊的實現就方便得多了。)

課程成績統計

同樣地,在這里我也做了一個圖形的統計,利用柱狀圖展示了各個分數段的人數,方便老師快速了解成績的分布情況。然后利用了餅狀圖分析了及格人數/不及格人數的比例,因為在這里不及格的人數為0,所以整塊都是及格的藍色。

畫圖的代碼如下(有了上一張圖的經驗,這張就好多了):

## 繪制統計試圖
def print_statistics_view(stu_list):
    ##### 數據設置
    range_number = [0,0,0,0,0]  #各分數段人數
    type_number = [0,0]          # 各類型人數[及格,不及格,缺考]
    
    for stu in stu_list:
        count_type(stu, type_number)
        count_range(stu, range_number)
    #### 開始繪圖
    fig = plt.figure(figsize=(8, 4), dpi=85)  #整體圖的標題
    colors = ['#7199cf', '#4fc4aa', '#00BFFF', '#FF7F50', '#BDB76B']
    #①在121位置上添加柱圖,通過fig.add_subplot()加入子圖
    ax = fig.add_subplot(121)  
    ax.set_title('各分數段人數統計', fontproperties='SimHei')  #子圖標題
    xticks = np.arange(len(range_number))  #生成x軸每個元素的位置
    bar_width = 0.5  #定義柱狀圖每個柱的寬度
    
    #設置x軸標簽
    score_range = ['[0,60)','[60,70)','[70,80)','[80,90)','[90,100]']
    ax.set_xticklabels(score_range) 
    ax.set_xticks(xticks)  #設置x軸上每個標簽的具體位置
    #設置y軸的標簽
    ax.set_ylabel('人數', fontproperties='SimHei')  
    ax.bar(xticks, range_number, width=bar_width, color=colors, edgecolor='none')  #設置柱的邊緣為透明
    #②在122位置加入餅圖
    ax = fig.add_subplot(122)
    ax.set_title('及格\不及格占比')
    # 生成同時包含名稱和速度的標簽
    type_labels = ['及格','不及格']
    pie_labels = ['{}:{}人'.format(type_name, number) for type_name, number in zip(type_labels, type_number)]
    # 畫餅狀圖,並指定標簽和對應顏色
    #解決漢字亂碼問題
    matplotlib.rcParams['font.sans-serif']=['SimHei']  #使用指定的漢字字體類型(此處為黑體)
    
    ax.pie(type_number, labels=pie_labels, colors=colors, autopct='%1.2f%%')
    ax.axis('equal')   #保證餅圖不變形
    plt.show()

2.7 保存學生信息到文件中

在保存到文件時,默認保存到程序目錄下的data_file目錄里面,用戶可以手動輸入文件名,也可以直接回車使用默認選項(防止用戶懶得輸入這么麻煩的東西_)。

保存信息到文件

# 文件頭
STUDENT_LABEL = ['序號','姓名','學號','分數','排名','作業1','作業2','作業3','作業4', '測驗1', '測驗2', '實驗1', '實驗2']
FILE_DIR = './data_file/' #保存文件的目錄,默認為當前文件下的data_file目錄
# save to file保存到文件
def save_to_file(stu_list):
    print("\033[34m",end='')
    fn = input("請輸入文件名(例如: a.csv, 直接回車則默認為[學生成績信息.csv]) >> ")
    print('\033[0m',end='')
    if fn == '': # 默認選項
        fn = '學生成績信息.csv'
    elif len(fn) < 5: # 該用戶沒有輸入后綴名
        fn = fn + '.csv'
    elif fn[-4:] != '.csv': # 該用戶沒有輸入后綴名
        fn = fn + '.csv'
    all_values = []
    for index, stu in enumerate(stu_list):
        '''
        一個stu字典實體序列化成我們想要的格式,便於保存到文件
        index為保存到文件后該實體的序號,與list的序號對應
        '''
        stu_value = [index, stu['姓名'], stu['學號'], stu['分數'], index+1]
        stu_value = stu_value + stu['作業'] + stu['測驗'] + stu['實驗']
        all_values.append(stu_value)
    with open(FILE_DIR+fn,'w+',newline='') as f:
        writer = csv.writer(f)#創建一個csv的寫入器
        writer.writerow(STUDENT_LABEL)#寫入標簽
        writer.writerows(all_values) #寫入樣本數據
        f.close()
    print('\033[1;31m')  
    print("保存信息到["+FILE_DIR+fn+"]成功!")
    print('\033[0m')  

用戶輸入自定義的文件名后,由於保存的是CSV格式的文件,因此需要簡單修正一下用戶輸入的文件名(因為有時候可能沒有輸入后綴名之類的。),然后再讀取列表的數據,保存到文件中,如下:

保存到文件中的信息

可以看到,由於列表的數據始終是有序的,因此排名與序號是對應的。

2.8 從文件中讀取學生信息

從文件讀取信息時,遵循的格式和保存的格式是一致的。與從文件中添加信息不同的是,該功能讀取文件中所有的信息添加進一個新的列表,然后丟棄系統原有的列表,使用讀取文件生成的新列表。

從文件讀取學生信息

同時,從文件讀取信息時,也允許分數項缺失,如果缺失,則重新計算后存入列表中去。導入文件也提供了默認的文件:

# 從文件導入信息
# 需要遵循格式:['序號','姓名','學號','分數','排名','作業1','作業2','作業3','作業4', '測驗1', '測驗2', '實驗1', '實驗2']
def load_from_file():
    print("\033[34m",end='')
    fn = input("請輸入文件路徑(例如: C:/a.csv, 直接回車則默認為[./data_file/學生成績信息.csv]) >> ")
    print('\033[0m',end='')
    file_path = FILE_DIR+'學生成績信息.csv' # 默認選項
    if fn != '':
        file_path = fn
    stu_list = []
    n = 0
    with open(file_path) as csvfile:
        csv_reader = csv.reader(csvfile)  # 使用csv.reader讀取csvfile中的文件
        next(csv_reader)  # 跳過文件頭
        for row in csv_reader:  # 讀取數據
            work       = [float(x) for x in row[5:9]] #轉化作業成績
            test       = [float(x) for x in row[9:11]] #轉化測驗成績
            experiment = [float(x) for x in row[11:]] #轉化實驗成績
            score = 0
            if row[3] == '':
                score = calc_score(work, test, experiment) # 考慮到成績位置為空的情況,重新計算成績。
            else:
                score = float(row[3])
            stu_info = {'姓名':row[1], '學號':row[2], '作業':work,
                    '測驗':test, '實驗': experiment, '分數':score}
            stu_list.append(stu_info) #
            n = n + 1
    # 如果讀取的是本程序輸出的,按理說不用排序
    # 但也可能是從其他文件讀入的數據,所以還是得做一下排序。
    stu_list.sort(key=lambda d:(d["分數"],np.mean(d["作業"]),np.mean(d["測驗"]),np.mean(d["實驗"])), reverse = True) # 排好序
    print('\033[1;31m')  
    print("從文件["+file_path+"]導入成功!共 "+str(n)+" 條信息!")
    print('\033[0m')  

    return stu_list

2.9 退出

在退出的時候,我做了一個小提示,提示用戶是否保存當前數據到文件中去。因為有時候如果不提醒用戶的話,用戶可能由於疏忽而忘記了保存到文件,一旦退出程序則數據就丟失了。

退出提示是否保存數據到文件

3 小結

這個程序斷斷續續寫了好久,主要是想把這個作業給做的完善一些。盡管這是一個小小的project,但是如果能充分考慮各方面的因素,功能上做到盡可能完美,程序上盡可能做到健壯,也是一件並不簡單的事情。

當然了,一些元素都是基於我自己個人的簡單思考而設計實現的需求,並沒有做過相關實際的調研問詢,所可能會存在不合理的地方,希望各位讀者嘴下留情。​

欲下載本文相關的完整代碼及算例,請關注公眾號【程序猿聲】,后台回復【Python成績管理】不包括【】即可


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM