前言
大家好,這學期上了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成績管理】不包括【】即可