10分鐘教你用Python打造學(xué)生成績管理系統(tǒng)

代碼黑科技的分享區(qū)
?大家好,這學(xué)期上了Python這門課,然后結(jié)課的時候老師要求做一個這樣的學(xué)生管理系統(tǒng)。自己按照老師的要求寫了一下,今天就把這個小程序分享出來吧~供Python新手小朋友學(xué)習(xí)~

其實類似這類信息管理系統(tǒng)之類的程序,核心還是和數(shù)據(jù)打交道吧,包括增刪查改,讀取、展示、保存等。
在數(shù)據(jù)結(jié)構(gòu)上,我依然用了給定的數(shù)據(jù)結(jié)構(gòu),即:
score1?=?{?"姓名":"張三豐",?
?????????"學(xué)號":"U19990001",?
?????????"作業(yè)"?:?[80,?64,?67,?20],?
?????????"測驗"?:?[75,?75],?
?????????"實驗"?:?[78,?57]?,
?????????"分?jǐn)?shù)"?:?0
???????}?
沒有增加新的字段比如排名之類的。這樣做的主要是考慮到排名、平均成績等均可以由上述結(jié)構(gòu)中的信息計算出來,而且也可以避免因為一個某個成績變動,導(dǎo)致一系列的數(shù)據(jù)需要重新計算。畢竟,數(shù)據(jù)存儲得越多,維護(hù)起來的難度就越大,特別是一些關(guān)聯(lián)密切的數(shù)據(jù)更是如此。
在存儲在結(jié)構(gòu)上,我采用了Python中常用的列表作為此程序的“數(shù)據(jù)庫”,因為列表操作起來還是非常方便的。此外,因為這里涉及到一個排名的問題,所以我制定了一個原則:在列表中的所有數(shù)據(jù)實體都是按照成績高低進(jìn)行排序的,即整個存儲信息的列表由始至終都是有序的。這樣就解決了排名的問題,至于如何實現(xiàn)的,后續(xù)我會進(jìn)行闡述。
運(yùn)行環(huán)境采用的是Windows 10 x64位操作系統(tǒng)+anaconda(Python3.7)+Spyder,默認(rèn)情況下即可運(yùn)行,不需要安裝其他庫。
這一節(jié)將介紹一下該程序相應(yīng)的功能以及相應(yīng)的代碼實現(xiàn)。在此之前先介紹設(shè)定的一些規(guī)則:
> 計算成績時取小數(shù)點(diǎn)后三位。
> 排名根據(jù)[分?jǐn)?shù)、作業(yè)平均、測驗平均、實驗平均]的優(yōu)先級比較。不存在排名相同的情況。如果這4項指標(biāo)都相同,emmm應(yīng)該不會有這么巧的事情。
> 文件保存和讀取時,采取CSV格式的數(shù)據(jù)文件。文件頭遵循['序號','姓名','學(xué)號','分?jǐn)?shù)','排名','作業(yè)1','作業(yè)2','作業(yè)3','作業(yè)4', '測驗1', '測驗2', '實驗1', '實驗2']這種格式。
2.0 主界面
整個程序的主界面如下:

在整個程序的交互中,為了更好提高提示信息的辨識度,系統(tǒng)規(guī)定了幾種顏色:
-?藍(lán)色提示內(nèi)容表示需要用戶輸入相關(guān)信息。
- 紅色表示系統(tǒng)執(zhí)行指令的結(jié)果,比如成功,失敗等等。
- 黑色表示系統(tǒng)菜單顯示啊,查詢結(jié)果的輸出等。
2.1 添加學(xué)生信息
在添加學(xué)生信息中,在實現(xiàn)了手動添加信息的基礎(chǔ)上,我又增加了從文件中導(dǎo)入信息的功能。不過在添加信息這塊,我做了一個約束:添加學(xué)生信息時,如果系統(tǒng)中已經(jīng)存在該學(xué)生的學(xué)號,則不能重復(fù)添加。兩種方式都遵循該原則,以保證學(xué)號的唯一性。

在添加學(xué)生信息時,因為前面說了列表里面的數(shù)據(jù)需要保持有序性,所以采取了插入排序的方式進(jìn)行添加,核心的代碼如下:
#?根據(jù)優(yōu)先級[分?jǐn)?shù)、作業(yè)平均、測驗平均、實驗平均]比較s1是否優(yōu)于s2
def?cmp_student(s1,?s2):
????if?s1["分?jǐn)?shù)"]?!=?s2["分?jǐn)?shù)"]:
????????return?s1["分?jǐn)?shù)"]?>?s2["分?jǐn)?shù)"]
????else:
????????if?np.mean(s1["作業(yè)"])?!=?np.mean(s2["作業(yè)"]):
????????????return?np.mean(s1["作業(yè)"])?>?np.mean(s2["作業(yè)"])
????????else:
????????????if?np.mean(s1["測驗"])?!=?np.mean(s2["測驗"]):
????????????????return?np.mean(s1["測驗"])?>?np.mean(s2["測驗"])
????????????else:
????????????????return?np.mean(s1["實驗"])?>?np.mean(s2["實驗"])
#?根據(jù)分?jǐn)?shù)大小,將學(xué)生信息插入到列表中,插入排序
def?add_to_list(stu,?stu_list):
????if?len(stu_list):
????????if?cmp_student(stu,?stu_list[0]):?#?比第一名還優(yōu)秀
????????????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)
原諒我寫了這么多if!

手動添加時,逐個輸入學(xué)生的信息,最后按照分?jǐn)?shù)插入到相應(yīng)的位置,注意的是,需要保證在輸入成績時確保獲取的是數(shù)字,否則提示錯誤需要用戶重新輸入:
#?輸入一個數(shù)字
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("輸入有誤,請輸入一個數(shù)字!")
????????????print('\033[0m',end='')
注:類似print("\033[34m",end='')這類語句是控制輸出的字體顏色的。下同
從文件中添加時,系統(tǒng)提供了默認(rèn)文件的選項,直接回車則默認(rèn)從data_file目錄下的學(xué)生成績信息.csv文件導(dǎo)入,因為有些用戶是懶得輸入文件名的。需要注意的是,導(dǎo)入的文件中,允許成績選項缺失,如果缺失了,則利用其它成績重新計算得出。但其它必要信息不能缺失:
#?從文件添加學(xué)生信息
#?需要遵循格式:['序號','姓名','學(xué)號','分?jǐn)?shù)','排名','作業(yè)1','作業(yè)2','作業(yè)3','作業(yè)4', '測驗1', '測驗2', '實驗1', '實驗2']
def?add_from_file(stu_list):
????print("\033[34m",end='')
????fn?=?input("請輸入文件路徑(例如:?C:/a.csv,?直接回車則默認(rèn)為[./data_file/學(xué)生成績信息.csv])?>>?")
????print("\033[0m",end='')
????file_path?=?'./data_file/'+'學(xué)生成績信息.csv'?#?默認(rèn)選項
????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:??#?讀取數(shù)據(jù)
????????????if?find_student_uid(row[2],?stu_list)?!=?-INF:?#?如果存在學(xué)號相同,則不添加
????????????????n_du?=?n_du?+?1
????????????????continue
????????????work???????=?[float(x)?for?x?in?row[5:9]]?#轉(zhuǎn)化作業(yè)成績
????????????test???????=?[float(x)?for?x?in?row[9:11]]?#轉(zhuǎn)化測驗成績
????????????experiment?=?[float(x)?for?x?in?row[11:]]?#轉(zhuǎn)化實驗成績
????????????score?=?0
????????????if?row[3]?==?'':
????????????????score?=?calc_score(work,?test,?experiment)?#?考慮到成績位置為空的情況,重新計算成績。
????????????else:
????????????????score?=?float(row[3])
????????????stu_info?=?{'姓名':row[1],?'學(xué)號':row[2],?'作業(yè)':work,
????????????????????'測驗':test,?'實驗':?experiment,?'分?jǐn)?shù)':score}
????????????add_to_list(stu_info,stu_list)???#將字典數(shù)據(jù)添加到列表中,插入排序。
????????????n?=?n?+?1
????print('\033[1;31m')??
????print("從文件["+file_path+"]添加信息成功!共添加?"+str(n)+"?條信息,跳過?"+str(n_du)+"?條重復(fù)信息!")
????print('\033[0m')??
????return?stu_list
2.2 修改學(xué)生信息
這一塊比較簡單,找到學(xué)生信息后,輸入相應(yīng)信息然后修改。大部分都是提示輸入的語句。

不過需要注意的是,修改了相應(yīng)的作業(yè)、實驗等成績后,需要更新學(xué)生的分?jǐn)?shù),同時重新計算學(xué)生的排名,將該生挪到列表的相應(yīng)位置上。具體做法在我的代碼實現(xiàn)中比較簡單,先將該生從列表中移除,重新計算分?jǐn)?shù)后再按照插入排序的思路放進(jìn)列表即可。這樣速度可能會快一些。因為變動信息的只有一個學(xué)生,如果再次對整個列表進(jìn)行排序可能會造成比較大的開銷。
2.3 刪除學(xué)生信息
這一塊也相對來說比較簡單,找到學(xué)生后,如果確認(rèn)刪除,則直接刪除該學(xué)生即可。刪除后其他學(xué)生的次序依然是有序的,無需再做調(diào)整。

2.4 查找學(xué)生信息
查找學(xué)生相關(guān)信息是通過`學(xué)號`遍歷列表進(jìn)行搜尋,找到后輸出學(xué)生的相關(guān)信息。

不過我在此基礎(chǔ)上,對學(xué)生成績進(jìn)行了簡單的統(tǒng)計,并通過圖表的方式進(jìn)行呈現(xiàn)。能夠讓老師或?qū)W生更直觀地看到各科成績的詳細(xì)內(nèi)容,找出自己的優(yōu)勢與不足,便于下次努力改進(jìn)。(不過這里因為想把兩個圖拼在一個圖上,因為不熟悉操作做了好久^~^)
bar1_colors?=?['#7199cf','#4fc4aa','#e1a7a2']
labels?=?np.array(['作業(yè)1','作業(yè)2','作業(yè)3','作業(yè)4','測驗1','測驗2','實驗1','實驗2'])
name=['作業(yè)','測驗','實驗']
#?統(tǒng)計學(xué)生成績等信息
def?statistics_student(stu):
????#=======自己設(shè)置開始============
????#標(biāo)簽
????#數(shù)據(jù)個數(shù)
????dataLenth?=?len(stu["作業(yè)"])+len(stu["測驗"])+len(stu["實驗"])
????#數(shù)據(jù)
????all_scores?=?stu["作業(yè)"]?+?stu["測驗"]?+?stu["實驗"]
????data?=?np.array(all_scores)
????average_score=[np.mean(stu["作業(yè)"]),np.mean(stu["測驗"]),np.mean(stu["實驗"])]
????
????#========自己設(shè)置結(jié)束============
????
????angles?=?np.linspace(0,?2*np.pi,?dataLenth,?endpoint=False)
????data?=?np.concatenate((data,?[data[0]]))?#?閉合?#?#將數(shù)據(jù)結(jié)合起來
????angles?=?np.concatenate((angles,?[angles[0]]))?#?閉合
????
????fig?=?plt.figure(figsize=(8,?4.2),?dpi=80)
????ax?=?fig.add_subplot(121,?polar=True)# polar參數(shù)!!121代表總行數(shù)總列數(shù)位置
????ax.plot(angles,?data,?'bo-',?linewidth=1)#?畫線四個參數(shù)為x,y,標(biāo)記和顏色,閑的寬度
????ax.fill(angles,?data,?facecolor='r',?alpha=0.1)#?填充顏色和透明度
????ax.set_thetagrids(angles?*?180/np.pi,?labels,?fontproperties='SimHei')
????ax.set_title("{}?詳細(xì)成績雷達(dá)圖".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)??#設(shè)置x軸上每個標(biāo)簽的具體位置
????ax.set_ylim([0,?100])?#?設(shè)置y軸范圍
????ax.bar(xticks,average_score,color=bar1_colors)
????ax.set_title("{}?平均成績柱狀圖".format(stu["姓名"]),fontproperties='SimHei')
????plt.show()2.5 打印全體學(xué)生成績信息
這一個功能實現(xiàn)也蠻簡單,遍歷學(xué)生列表,然后調(diào)用打印函數(shù)逐個進(jìn)行打印輸出即可,這里輸出單個學(xué)生信息的時候就沒有輸出統(tǒng)計圖的信息了。主要是考慮到人數(shù)過多時,輸出圖的話,可能會導(dǎo)致速度過慢,影響體驗。輸出完成后會簡單統(tǒng)計一下一共有幾個人。

2.6 課程成績統(tǒng)計
在統(tǒng)計成績這個模塊中,由于數(shù)據(jù)在列表中已經(jīng)是有序的了,所以最高分最低分,中位數(shù)的獲取都比較容易。而平均分也可以很快得出。(其實我覺得,程序的整體結(jié)構(gòu)和思路做好以后,功能模塊的實現(xiàn)就方便得多了。)

同樣地,在這里我也做了一個圖形的統(tǒng)計,利用柱狀圖展示了各個分?jǐn)?shù)段的人數(shù),方便老師快速了解成績的分布情況。然后利用了餅狀圖分析了`及格人數(shù)/不及格人數(shù)`的比例,因為在這里不及格的人數(shù)為0,所以整塊都是及格的藍(lán)色。
畫圖的代碼如下(有了上一張圖的經(jīng)驗,這張就好多了):
##?繪制統(tǒng)計試圖
def?print_statistics_view(stu_list):
????#####?數(shù)據(jù)設(shè)置
????range_number?=?[0,0,0,0,0]??#各分?jǐn)?shù)段人數(shù)
????type_number?=?[0,0]??????????#?各類型人數(shù)[及格,不及格,缺考]
????
????for?stu?in?stu_list:
????????count_type(stu,?type_number)
????????count_range(stu,?range_number)
????####?開始繪圖
????fig?=?plt.figure(figsize=(8,?4),?dpi=85)??#整體圖的標(biāo)題
????colors?=?['#7199cf',?'#4fc4aa',?'#00BFFF',?'#FF7F50',?'#BDB76B']
????#①在121位置上添加柱圖,通過fig.add_subplot()加入子圖
????ax?=?fig.add_subplot(121)??
????ax.set_title('各分?jǐn)?shù)段人數(shù)統(tǒng)計',?fontproperties='SimHei')??#子圖標(biāo)題
????xticks?=?np.arange(len(range_number))??#生成x軸每個元素的位置
????bar_width?=?0.5??#定義柱狀圖每個柱的寬度
????
????#設(shè)置x軸標(biāo)簽
????score_range?=?['[0,60)','[60,70)','[70,80)','[80,90)','[90,100]']
????ax.set_xticklabels(score_range)?
????ax.set_xticks(xticks)??#設(shè)置x軸上每個標(biāo)簽的具體位置
????#設(shè)置y軸的標(biāo)簽
????ax.set_ylabel('人數(shù)',?fontproperties='SimHei')??
????ax.bar(xticks,?range_number,?width=bar_width,?color=colors,?edgecolor='none')??#設(shè)置柱的邊緣為透明
????#②在122位置加入餅圖
????ax?=?fig.add_subplot(122)
????ax.set_title('及格\不及格占比')
????#?生成同時包含名稱和速度的標(biāo)簽
????type_labels?=?['及格','不及格']
????pie_labels?=?['{}:{}人'.format(type_name,?number)?for?type_name,?number?in?zip(type_labels,?type_number)]
????#?畫餅狀圖,并指定標(biāo)簽和對應(yīng)顏色
????#解決漢字亂碼問題
????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 保存學(xué)生信息到文件中
在保存到文件時,默認(rèn)保存到程序目錄下的data_file目錄里面,用戶可以手動輸入文件名,也可以直接回車使用默認(rèn)選項(防止用戶懶得輸入這么麻煩的東西^_^)。

#?文件頭
STUDENT_LABEL?=?['序號','姓名','學(xué)號','分?jǐn)?shù)','排名','作業(yè)1','作業(yè)2','作業(yè)3','作業(yè)4',?'測驗1',?'測驗2',?'實驗1',?'實驗2']
FILE_DIR?=?'./data_file/'?#保存文件的目錄,默認(rèn)為當(dāng)前文件下的data_file目錄
#?save?to?file保存到文件
def?save_to_file(stu_list):
????print("\033[34m",end='')
????fn?=?input("請輸入文件名(例如:?a.csv,?直接回車則默認(rèn)為[學(xué)生成績信息.csv])?>>?")
????print('\033[0m',end='')
????if?fn?==?'':?#?默認(rèn)選項
????????fn?=?'學(xué)生成績信息.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的序號對應(yīng)
????????'''
????????stu_value?=?[index,?stu['姓名'],?stu['學(xué)號'],?stu['分?jǐn)?shù)'],?index+1]
????????stu_value?=?stu_value?+?stu['作業(yè)']?+?stu['測驗']?+?stu['實驗']
????????all_values.append(stu_value)
????with?open(FILE_DIR+fn,'w+',newline='')?as?f:
????????writer?=?csv.writer(f)#創(chuàng)建一個csv的寫入器
????????writer.writerow(STUDENT_LABEL)#寫入標(biāo)簽
????????writer.writerows(all_values)?#寫入樣本數(shù)據(jù)
????????f.close()
????print('\033[1;31m')??
????print("保存信息到["+FILE_DIR+fn+"]成功!")
????print('\033[0m')??
用戶輸入自定義的文件名后,由于保存的是CSV格式的文件,因此需要簡單修正一下用戶輸入的文件名(因為有時候可能沒有輸入后綴名之類的。),然后再讀取列表的數(shù)據(jù),保存到文件中,如下:

可以看到,由于列表的數(shù)據(jù)始終是有序的,因此排名與序號是對應(yīng)的。
2.8 從文件中讀取學(xué)生信息
從文件讀取信息時,遵循的格式和保存的格式是一致的。與從文件中添加信息不同的是,該功能讀取文件中所有的信息添加進(jìn)一個新的列表,然后丟棄系統(tǒng)原有的列表,使用讀取文件生成的新列表。

同時,從文件讀取信息時,也允許分?jǐn)?shù)項缺失,如果缺失,則重新計算后存入列表中去。導(dǎo)入文件也提供了默認(rèn)的文件:
#?從文件導(dǎo)入信息
#?需要遵循格式:['序號','姓名','學(xué)號','分?jǐn)?shù)','排名','作業(yè)1','作業(yè)2','作業(yè)3','作業(yè)4', '測驗1', '測驗2', '實驗1', '實驗2']
def?load_from_file():
????print("\033[34m",end='')
????fn?=?input("請輸入文件路徑(例如:?C:/a.csv,?直接回車則默認(rèn)為[./data_file/學(xué)生成績信息.csv])?>>?")
????print('\033[0m',end='')
????file_path?=?FILE_DIR+'學(xué)生成績信息.csv'?#?默認(rèn)選項
????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:??#?讀取數(shù)據(jù)
????????????work???????=?[float(x)?for?x?in?row[5:9]]?#轉(zhuǎn)化作業(yè)成績
????????????test???????=?[float(x)?for?x?in?row[9:11]]?#轉(zhuǎn)化測驗成績
????????????experiment?=?[float(x)?for?x?in?row[11:]]?#轉(zhuǎn)化實驗成績
????????????score?=?0
????????????if?row[3]?==?'':
????????????????score?=?calc_score(work,?test,?experiment)?#?考慮到成績位置為空的情況,重新計算成績。
????????????else:
????????????????score?=?float(row[3])
????????????stu_info?=?{'姓名':row[1],?'學(xué)號':row[2],?'作業(yè)':work,
????????????????????'測驗':test,?'實驗':?experiment,?'分?jǐn)?shù)':score}
????????????stu_list.append(stu_info)?#
????????????n?=?n?+?1
????#?如果讀取的是本程序輸出的,按理說不用排序
????#?但也可能是從其他文件讀入的數(shù)據(jù),所以還是得做一下排序。
????stu_list.sort(key=lambda?d:(d["分?jǐn)?shù)"],np.mean(d["作業(yè)"]),np.mean(d["測驗"]),np.mean(d["實驗"])),?reverse?=?True)?#?排好序
????print('\033[1;31m')??
????print("從文件["+file_path+"]導(dǎo)入成功!共?"+str(n)+"?條信息!")
????print('\033[0m')??
????return?stu_list2.9 退出
在退出的時候,我做了一個小提示,提示用戶是否保存當(dāng)前數(shù)據(jù)到文件中去。因為有時候如果不提醒用戶的話,用戶可能由于疏忽而忘記了保存到文件,一旦退出程序則數(shù)據(jù)就丟失了。

這個程序斷斷續(xù)續(xù)寫了好久,主要是想把這個作業(yè)給做的完善一些(因為小編有各種強(qiáng)迫癥)。盡管這是一個小小的project,但是如果能充分考慮各方面的因素,功能上做到盡可能完美,程序上盡可能做到健壯,也是一件并不簡單的事情。
當(dāng)然了,一些元素都是基于我自己個人的簡單思考而設(shè)計實現(xiàn)的需求,并沒有做過相關(guān)實際的調(diào)研問詢,所可能會存在不合理的地方。希望各位讀者嘴下留情。如果喜歡的話,各位可以點(diǎn)個在看嘛!

完整源代碼在公眾號后臺回復(fù)【Python成績管理】即可獲取
推薦閱讀:
干貨 | 想學(xué)習(xí)優(yōu)化算法,不知從何學(xué)起?
干貨 | 運(yùn)籌學(xué)從何學(xué)起?如何快速入門運(yùn)籌學(xué)算法?
干貨 | 學(xué)習(xí)算法,你需要掌握這些編程基礎(chǔ)(包含JAVA和C++)
干貨 | 算法學(xué)習(xí)必備訣竅:算法可視化解密
干貨 | 模擬退火、禁忌搜索、迭代局部搜索求解TSP問題Python代碼分享記得點(diǎn)個在看支持下哦~

