
前一段时间给大家分享了一系列有关VBA编程的文章,如《VBA编程——批量重命名文件》《VBA编程——快速生成三好、合格奖励学生名单》《VBA编程——秒做奖励决定附件》《VBA编程——通过自定义函数实现十万阿拉伯数字和中文数字的转换》,感受到了VBA功能的强大。VBA的优势在于依托Office而存在,不需要额外安装编程软件,编程后的程序大约只有几十kB,体积小,便于存储。但VBA和Python相比,其编程功能仅仅是Python的冰山一角。今天我们就用Python来编写一款防作弊出题系统。










Python防作弊出题系统V1.0代码
import randomfrom openpyxl import load_workbookfrom docx import Documentfrom docx.shared import Pt ,Cm # 用于设定字体大小(磅值)from docx.oxml.ns import qn # 用于应用中文字体from datetime import datetimefrom docx.enum.text import WD_ALIGN_PARAGRAPHfrom PIL import Image# 不重复随机整数生成函数def Random_num(maxNum, questNum):num_list = [] # 储存生成的随机数if questNum == maxNum :num_list = list(range(2,maxNum + 2))random.shuffle(num_list)else:while len(num_list) < questNum: # 控制随机数的个数num = random.randint(2, maxNum + 1) # 设定在此范围内取数if num in num_list: # 判断随机数是否重复continue # 若重复,则重新生成else:num_list.append(num) # 将不重复的随机数放入列表return num_list # 生成完成后返回随机数列表# 定义函数,按随机数在题库中抽取对应编号的题目def Question(que_type, numbers):'''numbers:需要抽取的试题编号'''questions = [] # 储存抽取的题目wb = load_workbook("题库.xlsx") # 载入题库right_answer = ''if que_type == "单选题":ws = wb[que_type]for i in numbers: # 按随机生成的编号抽题options = []question = ws.cell(i,2).value # 问题在B列options = [ws.cell(i,3).value,ws.cell(i,4).value,ws.cell(i,5).value,ws.cell(i,6).value]random.shuffle(options)answerA = "A.\t" + str(options[0]) # "\t"相当于按一下tab键answerB = "B.\t" + str(options[1])answerC = "C.\t" + str(options[2])answerD = "D.\t" + str(options[3])if ws.cell(i,7).value == 'A':right_answer = ws.cell(i,3).valueif ws.cell(i,7).value == 'B':right_answer = ws.cell(i,4).valueif ws.cell(i,7).value == 'C':right_answer = ws.cell(i,5).valueif ws.cell(i,7).value == 'D':right_answer = ws.cell(i,6).valueif right_answer == str(options[0]):right_answer = 'A'if right_answer == str(options[1]):right_answer = 'B'if right_answer == str(options[2]):right_answer = 'C'if right_answer == str(options[3]):right_answer = 'D'pic_address = ws.cell(i,9).valuesingle_question = [question, answerA, answerB, answerC, answerD, right_answer,pic_address] # 每行的数据存入列表questions.append(single_question) # 每个题目的数据存入总列表else: # 判断题和填空题,内容只取题干和答案ws = wb[que_type]for i in numbers:question = ws.cell(i,2).valueright_answer = ws.cell(i,3).valuepic_address = ws.cell(i, 5).valuesingle_question = [question, right_answer,pic_address]questions.append(single_question)wb.close()return questions# 写入考试题到word文件def To_word(number, questions_data,examNum):doc = Document("试题模板.docx")p = doc.add_paragraph() # 插入段落p.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER # 设置段落居中对齐r = p.add_run(examNum) # 插入文字块r.bold = False # 字体加粗r.font.size = Pt(10.5) # 字号设为10.5磅——五号r.font.name = '宋体' # 控制是西文时的字体r.element.rPr.rFonts.set(qn('w:eastAsia'), '宋体') # 控制是中文时的字体#分成两栏# section = doc.sections[0]# section._sectPr.xpath('./w:cols')[0].set(qn('w:num'), '2')# 写入单选题title1 = "一、单项选择题(共25题,每题2分)"p = doc.add_paragraph() # 插入段落r = p.add_run(title1) # 插入文字块r.bold = False # 字体加粗r.font.size = Pt(10.5) # 字号设为10.5磅r.font.name = u'黑体'r.element.rPr.rFonts.set(qn('w:eastAsia'), '黑体') # 控制是中文时的字体for index, i in enumerate(questions_data["单选题"], start=1): # 给题目从1开始编号paraghNum = 0doc.add_paragraph(f"{index}. {i[0]}") # 题干部分在单独一段paraghNum = paraghNum +1if len(i[1]) + len(i[2]) < 20:doc.add_paragraph(f"\t{i[1]}\t\t{i[2]}") # 选项A和选项B在同一段落paraghNum = paraghNum + 1else:doc.add_paragraph(f"\t{i[1]}")doc.add_paragraph(f"\t{i[2]}")paraghNum = paraghNum + 2if len(i[3]) + len(i[4]) < 20:doc.add_paragraph(f"\t{i[3]}\t\t{i[4]}") # 选项C和选项D在同一段落paraghNum = paraghNum + 1else:doc.add_paragraph(f"\t{i[3]}")doc.add_paragraph(f"\t{i[4]}")paraghNum = paraghNum + 2for paragh in range(-paraghNum,0):for run in doc.paragraphs[paragh].runs:run.font.size = Pt(10.5) # 字号设为10.5磅——五号run.font.name = '宋体' # 控制是西文时的字体run.element.rPr.rFonts.set(qn('w:eastAsia'), '宋体') # 控制是中文时的字体if i[6]:image = Image.open(i[6])width = image.widthheight = image.heightn = width / heightif n > 1:new_width = Cm(3.5)new_height = new_width / nif width > (2.5 * height):new_width = Cm(6)new_height = new_width / nelse:new_height = Cm(3.5)new_width = new_height * ndoc.add_picture(i[6],width=new_width,height=new_height)doc.paragraphs[-1].paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER# 写入填空题title2 = "二、填空题(共10题,每题2分)"p = doc.add_paragraph()r = p.add_run(title2)r.bold = Falser.font.size = Pt(10.5) # 字号设为10.5磅r.font.name = u'黑体'r.element.rPr.rFonts.set(qn('w:eastAsia'), '黑体')for index, i in enumerate(questions_data["填空题"], start=1):doc.add_paragraph(f"\t{index}. {i[0]}")for run in doc.paragraphs[-1].runs:run.font.size = Pt(10.5) # 字号设为10.5磅——五号run.font.name = '宋体' # 控制是西文时的字体run.element.rPr.rFonts.set(qn('w:eastAsia'), '宋体') # 控制是中文时的字体if i[2]:image = Image.open(i[2])width = image.widthheight = image.heightn = width / heightif n > 1:new_width = Cm(3.5)new_height = new_width / nif width > (2.5 * height):new_width = Cm(6)new_height = new_width / nelse:new_height = Cm(3.5)new_width = new_height * ndoc.add_picture(i[2], width=new_width, height=new_height)doc.paragraphs[-1].paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER# 写入作图题title3 = "三、作图题(共2题,每题5分)"p = doc.add_paragraph()r = p.add_run(title3)r.bold = Falser.font.size = Pt(10.5) # 字号设为10.5磅r.font.name = u'黑体'r.element.rPr.rFonts.set(qn('w:eastAsia'), '黑体')for index, i in enumerate(questions_data["作图题"], start=1):doc.add_paragraph(f"\t{index}. {i[0]}")for run in doc.paragraphs[-1].runs:run.font.size = Pt(10.5) # 字号设为10.5磅——五号run.font.name = '宋体' # 控制是西文时的字体run.element.rPr.rFonts.set(qn('w:eastAsia'), '宋体') # 控制是中文时的字体if i[2]:image = Image.open(i[2])width = image.widthheight = image.heightn = width / heightif n > 1:new_width = Cm(3.5)new_height = new_width / nif width > (2.5 * height):new_width = Cm(6)new_height = new_width / nelse:new_height = Cm(3.5)new_width = new_height * ndoc.add_picture(i[2], width=new_width, height=new_height)doc.paragraphs[-1].paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER# 写入计算题title4 = "四、计算题(共2题,每题10分)"p = doc.add_paragraph()r = p.add_run(title4)r.bold = Falser.font.size = Pt(10.5) # 字号设为10.5磅r.font.name = u'黑体'r.element.rPr.rFonts.set(qn('w:eastAsia'), '黑体')for index, i in enumerate(questions_data["计算题"], start=1):doc.add_paragraph(f"\t{index}. {i[0]}")for run in doc.paragraphs[-1].runs:run.font.size = Pt(10.5) # 字号设为10.5磅——五号run.font.name = '宋体' # 控制是西文时的字体run.element.rPr.rFonts.set(qn('w:eastAsia'), '宋体') # 控制是中文时的字体if i[2]:image = Image.open(i[2])width = image.widthheight = image.heightn = width / heightif n > 1:new_width = Cm(3.5)new_height = new_width / nif width > (2.5 * height):new_width = Cm(6)new_height = new_width / nelse:new_height = Cm(3.5)new_width = new_height * ndoc.add_picture(i[2], width=new_width, height=new_height)doc.paragraphs[-1].paragraph_format.alignment=WD_ALIGN_PARAGRAPH.RIGHTdoc.save(f"试卷及答案\\考试题{number}({examNum}).docx")# 写入答案def Answer(number, questions_data,examNum):doc = Document()# 全局字体设为“宋体”doc.styles['Normal'].font.name = u'宋体'doc.styles['Normal']._element.rPr.rFonts.set(qn('w:eastAsia'), u'宋体')title = "泾川二中八年级第二学期考试题(答案)"p = doc.add_paragraph()p.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER # 设置段落居中对齐r = p.add_run(title)r.bold = Truer.font.size = Pt(20)p = doc.add_paragraph() # 插入段落p.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER # 设置段落居中对齐r = p.add_run(examNum) # 插入文字块r.bold = True # 字体加粗r.font.size = Pt(10.5) # 字号设为12磅# 写入单选题答案title1 = "一、单项选择题答案(共25题,每题2分)"p = doc.add_paragraph()r = p.add_run(title1)r.bold = Falser.font.size = Pt(10.5) # 字号设为10.5磅——五号r.font.name = '黑体' # 控制是西文时的字体r.element.rPr.rFonts.set(qn('w:eastAsia'), '黑体')p = doc.add_paragraph()for index, i in enumerate(questions_data["单选题"], start=1):p.add_run(f"{index}. {i[-2]}\t")if index % 10 == 0: # 每段只显示10个答案p = doc.add_paragraph() # 满10个,则新建段落title2 = "二、填空题答案(共10题,每题2分)"p = doc.add_paragraph()r = p.add_run(title2)r.bold = Falser.font.size = Pt(10.5) # 字号设为10.5磅——五号r.font.name = '黑体' # 控制是西文时的字体r.element.rPr.rFonts.set(qn('w:eastAsia'), '黑体')p = doc.add_paragraph()for index, i in enumerate(questions_data["填空题"], start=1):p.add_run(f"{index}. {i[-2]}\t\t")if index % 2 == 0: # 每段只显示2个答案p = doc.add_paragraph() # 满2个,则新建段落title3 = "三、作图题(共2题,每题5分)"p = doc.add_paragraph()r = p.add_run(title3)r.bold = Falser.font.size = Pt(10.5) # 字号设为10.5磅——五号r.font.name = '黑体' # 控制是西文时的字体r.element.rPr.rFonts.set(qn('w:eastAsia'), '黑体')p = doc.add_paragraph()for index, i in enumerate(questions_data["作图题"], start=1):p.add_run(f"{index}. {i[-2]}\t\t")if index % 2 == 0: # 每段只显示2个答案p = doc.add_paragraph() # 满2个,则新建段落title4 = "四、计算题(共2题,每题10分)"p = doc.add_paragraph()r = p.add_run(title4)r.bold = Falser.font.size = Pt(10.5) # 字号设为10.5磅——五号r.font.name = '黑体' # 控制是西文时的字体r.element.rPr.rFonts.set(qn('w:eastAsia'), '黑体')p = doc.add_paragraph()for index, i in enumerate(questions_data["计算题"], start=1):p.add_run(f"{index}. {i[-2]}\t\t")if index % 2 == 0: # 每段只显示2个答案p = doc.add_paragraph() # 满2个,则新建段落doc.save(f"试卷及答案\\考试题{number}({examNum})答案.docx")# 主函数def main():test_paperNum = int(input('请输入试卷份数!'))for number in range(1, test_paperNum +1): # 不同的试卷数量max_num_single_choice = 0max_num_completion =0max_num_drawing = 0max_num_computational =0wb = load_workbook("题库.xlsx")while wb['单选题'].cell(max_num_single_choice + 2,2).value:max_num_single_choice = max_num_single_choice +1while wb['填空题'].cell(max_num_completion + 2,2).value:max_num_completion = max_num_completion +1while wb['作图题'].cell(max_num_drawing + 2,2).value:max_num_drawing = max_num_drawing +1while wb['计算题'].cell(max_num_computational + 2,2).value:max_num_computational = max_num_computational +1wb.close()# 生成随机题目编号num_single_choice = Random_num(max_num_single_choice, 25)num_completion = Random_num(max_num_completion, 10)num_drawing = Random_num(max_num_drawing, 2)num_computational = Random_num(max_num_computational, 2)# 将生成的编号存入字典`question_num`question_num = {"单选题号": num_single_choice,"填空题号": num_completion,"作图题号": num_drawing,"计算题号": num_computational}# 根据随机生成的题目编号去题库选题,并存入`questions_data`questions_data = {"单选题": Question("单选题", question_num["单选题号"]),"填空题": Question("填空题", question_num["填空题号"]),"作图题": Question("作图题", question_num["作图题号"]),"计算题": Question("计算题", question_num["计算题号"])}if number < 10:NumText = '00' + str(number)elif number <100:NumText = '0' + str(number)else:NumText = str(number)examNum = '试卷编号:' + datetime.now().strftime("%Y%m%d%H%M%S") + NumText# 将试题写入word文档,并保存To_word(number, questions_data,examNum)# 将试题答案写入word文档,并保存Answer(number, questions_data,examNum)print(f"试卷{number}({examNum})及答案完成!")if __name__ == '__main__':main()
当然,这只是用了近一周的时间写出来的无UI界面的1.0版本,主要是依据物理监测赋了试题分值。后续还准备抽时间编写带有UI界面的2.0版本,以及后期还准备研发成倍数修改题目数据的3.0版本,敬请关注!
编程不易,且用且珍惜!
感谢关注
2024.06.13


