你們好,這學期上了Python這門課,而後結課的時候老師要求作一個這樣的學生管理系統。本身按照老師的要求寫了一下,今天就把這個小程序分享出來吧供Python新手小朋友學習python
欲下載本文相關的完整代碼及算例,請關注公衆號【程序猿聲】,後臺回覆【Python成績管理】不包括【】便可數據庫
其實相似這類信息管理系統之類的程序,核心仍是和數據打交道吧,包括增刪查改,讀取、展現、保存等。小程序
在數據結構上,我依然用了老師給定的數據結構,即:數據結構
score1 = { "姓名":"張三丰", "學號":"U19990001", "做業" : [80, 64, 67, 20], "測驗" : [75, 75], "實驗" : [78, 57] , "分數" : 0 }
沒有增長新的字段好比排名之類的。這樣作的主要是考慮到排名、平均成績等都可以由上述結構中的信息計算出來,並且也能夠避免由於一個某個成績變更,致使一系列的數據須要從新計算。畢竟,數據存儲得越多,維護起來的難度就越大,特別是一些關聯密切的數據更是如此。app
在存儲在結構上,我採用了Python中經常使用的列表做爲此程序的「數據庫」,由於列表操做起來仍是很是方便的。此外,由於這裏涉及到一個排名的問題,因此我制定了一個原則:在列表中的全部數據實體都是按照成績高低進行排序的,即整個存儲信息的列表由始至終都是有序的。這樣就解決了排名的問題,至於如何實現的,後續我會進行闡述。函數
運行環境:採用的是Windows 10 x64位操做系統+anaconda(Python3.7)+Spyder,默認狀況下便可運行,不須要安裝其餘庫。學習
這一節我將介紹一下該程序相應的功能以及相應的代碼實現。在此以前先介紹我本身設定的一些規則:字體
[分數、做業平均、測驗平均、實驗平均]
的優先級比較。不存在排名相同的狀況。若是這4項指標都相同,emmm應該不會有這麼巧的事情。['序號','姓名','學號','分數','排名','做業1','做業2','做業3','做業4', '測驗1', '測驗2', '實驗1', '實驗2']
這種格式。整個程序的主界面以下,哈哈,擁有直男審美的我實在是沒有辦法進一步美化了。ui
在整個程序的交互中,爲了更好提升提示信息的辨識度,我規定了幾種顏色:spa
藍色
提示內容表示須要用戶輸入相關信息。紅色
表示系統執行指令的結果,好比成功,失敗等等。黑色
表示系統菜單顯示啊,查詢結果的輸出等。在添加學生信息中,在實現了手動添加信息的基礎上,我又增長了從文件中導入信息的功能。不過在添加信息這塊,我作了一個約束:添加學生信息時,若是系統中已經存在該學生的學號,則不能重複添加。兩種方式都遵循該原則,以保證學號的惟一性。
在添加學生信息時,由於前面說了列表裏面的數據須要保持有序性,因此我採起了插入排序的方式進行添加,核心的代碼以下:
# 根據優先級[分數、做業平均、測驗平均、實驗平均]比較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
這一塊比較簡單,找到學生信息後,輸入相應信息而後修改。大部分都是提示輸入的語句。
不過須要注意的是,修改了相應的做業、實驗等成績後,須要更新學生的分數,同時從新計算學生的排名,將該生挪到列表的相應位置上。具體作法在個人代碼實現中比較簡單,先將該生從列表中移除,從新計算分數後再按照插入排序的思路放進列表便可。這樣速度可能會快一些。由於變更信息的只有一個學生,若是再次對整個列表進行排序可能會形成比較大的開銷。
這一塊也相對來講比較簡單,找到學生後,若是確認刪除,則直接刪除該學生便可。刪除後其餘學生的次序依然是有序的,無需再作調整。
查找學生相關信息是經過學號
遍歷列表進行搜尋,找到後輸出學生的相關信息。
不過我在此基礎上,對學生成績進行了簡單的統計,並經過圖表的方式進行呈現。可以讓老師或學生更直觀地看到各科成績的詳細內容,找出本身的優點與不足,便於下次努力改進。(不過這裏由於想把兩個圖拼在一個圖上,由於不熟悉操做作了很久~)
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()
這一個功能實現也蠻簡單,遍歷學生列表,而後調用打印函數逐個進行打印輸出便可,這裏輸出單個學生信息的時候就沒有輸出統計圖的信息了。主要是考慮到人數過多時,輸出圖的話,可能會致使速度過慢,影響體驗。輸出完成後會簡單統計一下一共有幾我的。
在統計成績這個模塊中,因爲數據在列表中已是有序的了,因此最高分最低分,中位數的獲取都比較容易。而平均分也能夠很快得出。(其實我以爲,程序的總體結構和思路作好之後,功能模塊的實現就方便得多了。)
一樣地,在這裏我也作了一個圖形的統計,利用柱狀圖展現了各個分數段的人數,方便老師快速瞭解成績的分佈狀況。而後利用了餅狀圖分析了及格人數/不及格人數
的比例,由於在這裏不及格的人數爲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()
在保存到文件時,默認保存到程序目錄下的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
格式的文件,所以須要簡單修正一下用戶輸入的文件名(由於有時候可能沒有輸入後綴名之類的。),而後再讀取列表的數據,保存到文件中,以下:
能夠看到,因爲列表的數據始終是有序的,所以排名與序號是對應的。
從文件讀取信息時,遵循的格式和保存的格式是一致的。與從文件中添加信息不一樣的是,該功能讀取文件中全部的信息添加進一個新的列表,而後丟棄系統原有的列表,使用讀取文件生成的新列表。
同時,從文件讀取信息時,也容許分數項缺失,若是缺失,則從新計算後存入列表中去。導入文件也提供了默認的文件:
# 從文件導入信息 # 須要遵循格式:['序號','姓名','學號','分數','排名','做業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
在退出的時候,我作了一個小提示,提示用戶是否保存當前數據到文件中去。由於有時候若是不提醒用戶的話,用戶可能因爲疏忽而忘記了保存到文件,一旦退出程序則數據就丟失了。
這個程序斷斷續續寫了很久,主要是想把這個做業給作的完善一些。儘管這是一個小小的project,可是若是能充分考慮各方面的因素,功能上作到儘量完美,程序上儘量作到健壯,也是一件並不簡單的事情。
固然了,一些元素都是基於我本身我的的簡單思考而設計實現的需求,並無作過相關實際的調研問詢,所可能會存在不合理的地方,但願各位讀者嘴下留情。
欲下載本文相關的完整代碼及算例,請關注公衆號【程序猿聲】,後臺回覆【Python成績管理】不包括【】便可