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

前言

你們好,這學期上了Python這門課,而後結課的時候老師要求作一個這樣的學生管理系統。本身按照老師的要求寫了一下,今天就把這個小程序分享出來吧供Python新手小朋友學習python

欲下載本文相關的完整代碼及算例,請關注公衆號【程序猿聲】,後臺回覆【Python成績管理】不包括【】便可數據庫

1 整體構思

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

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

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

沒有增長新的字段好比排名之類的。這樣作的主要是考慮到排名、平均成績等都可以由上述結構中的信息計算出來,並且也能夠避免由於一個某個成績變更,致使一系列的數據須要從新計算。畢竟,數據存儲得越多,維護起來的難度就越大,特別是一些關聯密切的數據更是如此。app

在存儲在結構上,我採用了Python中經常使用的列表做爲此程序的「數據庫」,由於列表操做起來仍是很是方便的。此外,由於這裏涉及到一個排名的問題,因此我制定了一個原則:在列表中的全部數據實體都是按照成績高低進行排序的,即整個存儲信息的列表由始至終都是有序的。這樣就解決了排名的問題,至於如何實現的,後續我會進行闡述。函數

運行環境:採用的是Windows 10 x64位操做系統+anaconda(Python3.7)+Spyder,默認狀況下便可運行,不須要安裝其餘庫。學習

2 程序說明

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

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

2.0 主界面

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

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

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

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成績管理】不包括【】便可

相關文章
相關標籤/搜索