一、概述
1.项目名称:中小学数学卷子自动生成程序
2.编程语言:Python
3.目标:分析结对编程队友的个人项目代码,分析其代码的优缺点
二、整体架构
1.代码整体基于面向对象的思想,根据功能分为了数量相对较多的类,其类图如下:
根据类图可以总结其思路为:Account作为账户类可以继承出小初高三种类型,Block和Regulation两个类实现了题目生成的方法并提供给账户类的子类使用,user_login和user_generate两个函数通过调用Console主界面类中的函数实现登录和使用,登录时user_login函数将会根据Library中的Account用户列表中的信息验证登录.
2.分析:
优点:
1.基本基于面向对象的思想,如Account分出三种子类.
2.通过设计多个类分担了单一函数的功能使得每一个函数更加简洁
3.由多个关系密切的类组合在一起形成了一个逻辑严密相连的网状结构
缺点:
1.过于多的类使得整体过于复杂,增加了理解与排错的成本
2.类之间的关系过于复杂,出现了不必要的双向依赖,增加了理解难度与失去了易解释性与易修改性
3.在面向对象编程中不必要地使用了函数,user_login和user_generate函数完全可以作为Console类的函数而没有必要单独写出,其代码这样做一是破坏了面向对象的原则使得功能间的关系变得混乱复杂,二是通过函数来使用类的函数显得冗余而不必要.
4.设计出了不必要的类,Block类和Regulation类实现了生成题目的相似功能,同时两个类不是继承关系,而是相互依赖的关系使得类中的函数间功能分配不明,没有明显的配合作用.
三、核心代码分析
1.Account类
class Account(object): """ 生成题目系统的账户类(抽象类) 作为账户类的顶层抽象,记录每个账户的数据信息 Attributes: password:账户的密码 username:账户的用户名 type:账户的类型,用于提示信息中使用 filename:账户文件夹的名字,用于保存生成的题目 """ password: str = None username: str = None type: str = None filename: str = None # 文件夹题目的存放的实现 def __init__(self, pwd, user): """ 构造函数 传入用户名和密码,来生成一个账户,默认情况下文件夹名字与用户名相同 Args: pwd:传入的密码 user:传入的账户 """ self.password = pwd self.username = user self.filename = user if not os.path.exists(self.filename): os.makedirs(self.filename) def generate(self, ques_list: list[str]) -> str: """ 生成题目的函数 这里只是一个抽象方法,留给具体类实现 Args: ques_list:当前已经生成的题目的列表,将此作为参数保证与此次生成的也没有重复 """ pass def check_same(self, question: str) -> bool: """ 题目查重函数的实现 对当前账户的文件夹的题目进行遍历,保证当前账户文件夹下没有重复的题目 Args: question:当前进行查重的字符串 Return: 返回bool表达查重是否通过,True表示不重复,False表示重复 """ file_list = os.listdir(self.filename) # 打开文件夹 for filename in file_list: # 遍历文件 with open(self.filename + '/' + filename, 'r', encoding='UTF-8') as file: history = file.readlines() for tmp in history: if question in tmp: # 有重复的题目就返回false return False return True # 最后都查重通过就返回TrueView Code
分析:代码实现了账户的设置,与题目的查重,把具体生成题目的实现留给子类,优点是实现了子类通用的功能函数,缺点是查重函数check_name()与Account类之间关联性不强,在此处看到有些意义不明,还有属性filename作为账户文件夹的名字始终与账户名相同,一方面有利于后期的拓展,一方面在目前功能下有些冗余
2.Account的子类:Primary、Junior、Senior类
class Primary(Account): """ 小学账户类 继承于账户的具体类小学账户,重写了生成题目的函数,可以生成小学的题目 """ def __init__(self, pwd, user): """ 构造函数 除了调用了父类的构造函数,还将账户类型标记为小学 Args: pwd:传入的密码 user:传入的账户名 """ super().__init__(pwd, user) self.type = "小学" def generate(self, ques_list) -> str: """ 生成题目的函数小学版 生成小学难度的题目 Args: ques_list:当前已经生成的题目,传入避免与已经生成的题目重复 Return: 一个为小学题目样式的字符串 """ question: str = "" flag: bool = False # 标记是否重复 while not flag: # 重复则重新生成 number, calculate = Regulation.basic_ge(2) Regulation.add_bracket(calculate) question = Regulation.combine(number, calculate) flag = self.check_same(question) and question not in ques_list # 检查既不在之前的文本文件中,又不在这次已经生成的题目中 return question class Junior(Account): """ 初中账户类 继承于账户的具体类初中账户,重写了生成题目的函数,可以生成初中难度的题目 """ def __init__(self, pwd, user): """ 构造函数 除了调用了父类的构造函数,还将账户类型标记为初中 Args: pwd:传入的密码 user:传入的账户名 """ super().__init__(pwd, user) self.type = "初中" def generate(self, ques_list) -> str: """ 生成题目的函数初中版 生成初中难度的题目 Args: ques_list:当前已经生成的题目,传入避免与已经生成的题目重复 Return: 一个为初中题目样式的字符串 """ question: str = "" flag: bool = False # 判断是否重复的标记 while not flag: # 重复则重新生成 number, calculate = Regulation.basic_ge(1) Regulation.add_bracket(calculate) Regulation.add_square(number) question = Regulation.combine(number, calculate) flag = self.check_same(question) and question not in ques_list # 检查既不在之前的文本文件中,又不在这次已经生成的题目中 return question class Senior(Account): """ 高中账户类 继承于账户的具体类高中账户,重写了生成题目的函数,可以生成高中难度的题目 """ def __init__(self, pwd, user): """ 构造函数 除了调用了父类的构造函数,还将账户类型标记为高中 Args: pwd:传入的密码 user:传入的账户名 """ super().__init__(pwd, user) self.type = "高中" def generate(self, ques_list) -> str: """ 生成题目的函数高中版 生成高中难度的题目 Args: ques_list:当前已经生成的题目,传入避免与已经生成的题目重复 Return: 一个为高中题目样式的字符串 """ question: str = "" flag: bool = False # 判断是否重复的标志 while not flag: # 重复则重新生成 number, calculate = Regulation.basic_ge(1) Regulation.add_bracket(calculate) Regulation.add_square(number) Regulation.add_triangle(number) question = Regulation.combine(number, calculate) flag = self.check_same(question) and question not in ques_list # 检查既不在之前的文本文件中,又不在这次已经生成的题目中 return questionView Code
分析:三个子类继承了Account,实现了generate题目生成函数,体现了多态的思想,但是各个子类的generate函数高度相似,代码的重复度高
3.Block和Regulation类
class Block(object): """ 操作数的封装类 封装了操作数,便于进行操作数的初中以及高中难度的操作, 即添加平方,根号和三角函数,并且不重复添加 attributes: content:包含操作数的封装列表,储存了数字和平方,开方,三角函数的操作符 is_square:该数是否做过平方/开方操作的表示符,True为已经有相应操作 is_triangle:该数是否做过三角函数的表示符,True为已经有相应操作 locate:数字在content列表的对应下标位置,便于对数操作 """ content: list[str] = list() is_square: bool = None is_triangle: bool = None locate: int = None def __init__(self): """ 构造函数 随机生成一个范围内的数字,其它参数也均初始化 """ self.content = [str(random.randint(1, 100))] self.locate = 0 self.is_square = False self.is_triangle = False def add_square(self): """ 乘方操作内部实现 对数字进行平方/开方操作的函数实现,作为内部方法供外部调用 """ if not self.is_square: self.content.insert(self.locate + 1, random.choice(Regulation.square)) self.content.insert(0, '(') # 为确保平方/开方操作比较醒目,对外围进行添加括号 self.locate += 1 self.content.append(')') self.is_square = True def add_triangle(self): """ 三角函数添加的内部实现 对数字进行三角函数的函数实现,作为内部方法供外部调用 """ if not self.is_triangle: self.content.insert(self.locate, random.choice(Regulation.triangle)) self.locate += 1 self.is_triangle = True def tostring(self): """ 将content列表中的内容作为一整个字符串返回 """ return "".join(self.content) class Regulation(object): """ 生成题目的方法类 作为各种方法的封装,包含了从小学题目生成的高中题目生成方法的具体实现 Attributes: operator:基本运算符列表,作为运算符随机的列表库 triangle:三角函数列表,作为三角函数随机的列表库 square:平方/开方列表,作为乘方随机的列表库 """ operator = ('+', '-', '*', '/') triangle = ('cos', 'sin', 'tan') square = ('^2', '^1/2') @staticmethod def basic_ge(bottom: int): """用于生成小学题目的基本框架 负责生成随机的数字列表和随机的运算符列表 Args: bottom:指定操作数随机的下限,小学的题目至少两个操作数,其它的题目可以1个操作数 Return: number: 操作数的列表,里面为封装随机的操作数的Block类的列表 calculate:运算符的列表,对应与操作符之间的运算符的列表 """ num = random.randint(bottom, 5) # 生成随机的操作数数量 number: list[Block] = list() calculate: list[str] = list() for i in range(0, num - 1): tmp = Block() number.append(tmp) # 添加一个操作数 calculate.append(random.choice(Regulation.operator)) # 添加一个运算符 number.append(Block()) # 操作数比运算符多一个,所以最后还需自己加一个 return number, calculate @staticmethod def add_bracket(calculate: list[str]): """ 添加括号的函数 概率添加随机括号到运算符列表之中,实现添加括号 Args: calculate:运算符列表,存储连接数字的运算符 """ num = len(calculate) rate = random.uniform(0, 1) # 生成括号的概率 if num >= 2 and rate >= 0.5: # 运算符达到2个以上才有生成括号的必要 left = random.randint(0, num - 2) # 生成左括号在运算符列表的位置 right = random.randint(left + 1, num - 1) # 生成右括号在运算符列表的位置 calculate.insert(right, ')') # 先添加右括号确保左括号位置不影响 calculate.insert(left, '(') @staticmethod def add_square(number: list[Block]): """ 添加乘方的函数 添加乘方到数字列表之中,实现初中难度的乘方操作 Args: number:数字列表,存储题目的数字以及基于单个数字的操作 """ num = len(number) count = random.randint(1, 2) # 添加乘方操作的个数 for i in range(0, count): junior = random.randint(0, num - 1) # 对随机位置进行添加 number[junior].add_square() @staticmethod def add_triangle(number: list[Block]): """ 添加三家函数的函数 添加三角函数到数字列表之中,实现高中难度的三角函数操作 Args: number:数字列表,存储题目的数字以及基于单个数字的操作 """ num = len(number) count = random.randint(1, 2) # 添加三角函数的个数 for i in range(0, count): senior = random.randint(0, num - 1) # 对随机位置进行添加 number[senior].add_triangle() @staticmethod def combine(number: list[Block], calculate: list[str]) -> str: """ 合并列表生成字符串的函数 将数字列表与操作数列表进行合成,生成最终的题目字符串 Args: number:数字列表,存储题目的数字以及基于单个数字的操作 calculate:运算符列表,存储连接数字的运算符 Return: 最终返回的是一个字符串,将数字列表和运算符列表里面的东西合成为一个要求的题目 """ flag: bool = True # 指示此时添加数字还是运算符 question: list[str] = list() # 字符串列表,最后拼接出字符串 j = 0 # 数字列表的当前下标 k = 0 # 运算符列表的当前下标 for i in range(0, len(number) + len(calculate)): if flag: # 此时对数字列表进行操作 question.append(number[k].tostring()) k += 1 # 下标+1 flag = False # 切换为加入运算符 continue else: # 此时对运算符列表进行操作 if calculate[j] == '(' or calculate[j] == ')': # 对运算符的括号进行特判,不进行列表的切换 if calculate[j] == '(': question.insert(len(question) - 1, '(') # 加入在当前的数字左边 else: question.insert(len(question), ')') # 加入在当前的数字右边 else: question.append(calculate[j]) flag = True # 切换为数字列表 j += 1 # 下标+1 return ''.join(question)View Code
分析:Block封装了操作数,定义了关于题目生成状态的一些变量,并在函数中通过调用Regulation中的静态函数完成括号生成、平方生成等核心功能,Regulation通过定义静态函数提供给Block类函数共同完成功能.在Block类中的记录是否添加了平方等状态变量方便了对题目当前的生成状况的追踪,有利于调试与维护.Regulation使用静态函数一方面方便了其他类的调用,使得功能的实现不需要实体化Regulation类就可以实现,另一方面Regulation中的函数又不具有普适性,其必须要搭配Block才能完整实现,这就显得Regulation没有存在的必要.
4.Library类
class Library(object): """ 账户的数据库类 包含了一个账户的列表,用来维护所有账户 Attributes: list_array:账户的列表,包含所有的账户 """ list_array: list[Account] = init_data def search(self, password: str, username: str): """ 匹配函数 用来查找当前输入的账户密码是否能够匹配账户中的一个 Args: password:输入的密码 username:输入的账户名 Return: 如果成功查询到了账户,则返回此账户,否则返回空 """ for curr in self.list_array: if curr.password == password and curr.username == username: return curr return NoneView Code
分析:Library中有Account列表与登录匹配函数在整体中是作为一个用户库工作
5.Console类
class Console(object): """ 主界面类 负责整合上面的函数,实现页面所需要的登录,生成题目,切换的功能 Attributes: currAccount:当前登录时所属的账户 curr_condition:当前是否已经登录 """ currAccount: Account = None curr_condition: bool = False def login(self, username, password, database: Library): """ 登录函数 主要功能是负责接受密码和账户的传入,并检查是否有匹配的账户 Args: username:用户输入的用户名 password:用户输入的密码 database:账户库 """ if not self.curr_condition: self.currAccount = database.search(password, username) if self.currAccount is None: # 未找到的情况 print("请输入正确的用户名,密码\n") else: # 成功查找的情况 print(f"当前选择为{self.currAccount.type}出题") self.curr_condition = True else: print("当前已经登录") def generate_hw(self, num): """ 接受数字的处理函数 当用户输入的是一个数字的时候,对应于生成对应题目和退出账户两种操作 此函数中做判断并进行对应的处理 Args: num:输入的生成的题目数 """ if num == -1: # 输入为-1的情况 self.curr_condition = False print("成功退出当前用户") elif 10 <= num <= 30: # 输入是10-30的合乎规定的范围 ques_list: list[str] = list() # 存放本次已经生成的题目 curr_time = datetime.datetime.now() now_time = curr_time.strftime("%Y-%m-%d-%H-%M-%S") + '.txt' # 创建此次生成题目对应的文本文件 path = self.currAccount.filename + '/' + now_time # 完整的相对路径 file = open(path, 'a', encoding='UTF-8') for x in range(0, num): output: str = self.currAccount.generate(ques_list) ques_list.append(output) final = '题目%s: %s\n' % (str(x + 1), output) print(final) file.write(final + '\n') # 写入题目加上换行 file.close() else: # 输入的数字不符合规定范围 print("生成题目数为10-30之间,请重新输入") def change(self, type_kind: str): """ 切换难度的函数 根据对应的合理的字符串,切换为相应的难度 Args: type_kind:要切换的难度字符串 """ if type_kind == "小学": self.currAccount.type = "小学" self.currAccount = Primary(self.currAccount.password, self.currAccount.username) if type_kind == "初中": self.currAccount.type = "初中" self.currAccount = Junior(self.currAccount.password, self.currAccount.username) if type_kind == "高中": self.currAccount.type = "高中" self.currAccount = Senior(self.currAccount.password, self.currAccount.username) def is_login(self) -> bool: """ 判断登录状态的函数 用来判断当前是否登录的判别函数 Return: curr_condition:当前是否登录 """ return self.curr_conditionView Code
分析:Console是主界面类包含有登录,接收数字处理,变更题目类型,检测是否登录四个函数,同时有currAccount记录当前正在登录的账户,curr_condition记录登录的状态.
Console中虽然有login()和generate_hw()函数但是实现功能却还需要全局函数user_login()和user_generate() 其中如user_generate和generate_hw之间都只是对一个num进行处理没有分开的必要,完全可以合并它们,或者已在类中重新构建功能分配更加合理的函数
6.user_login()函数和user_generate()函数
def user_login(main_login: Console, database): """ 登录的最终包装的函数 负责完善主界面类因实现功能没做到的文本引导, 同时起到了分割字符串的作用,使得函数不至于过于冗长 Args: main_login:主界面类,实现具体的各种功能 database:账户数据类,存放所有的账户 """ message: str = input("请输入用户名和密码:") content = message.split() if len(content) == 2: name: str = content[0] message: str = content[1] main_login.login(name, message, database) else: print("输入的文本数目有误,应为用户名和密码两个文本,中间以空格分割") def user_generate(main_login: Console): """ 登陆后的所有操作函数 包括了生成题目,切换难度和退出账户等所有的登录状态下的操作 Args: main_login:主界面类,实现各种具体的功能 """ num = input(f"准备生成{main_login.currAccount.type}数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):") if num.isdigit(): # 接受纯数字字符串,用于用户输入题目数的情况 main_login.generate_hw(int(num)) elif num == '-1': # 退出账户的操作 main_login.generate_hw(int(num)) else: # 其它情况的文本只有切换是有效的 if num[0:3] == '切换为': if num[3:len(num)] == '小学' or num[3:len(num)] == '初中' or num[3:len(num)] == '高中': main_login.change(num[3:len(num)]) else: print("请输入小学,初中,高中三个选项中的一个") else: print("无效字符串,请输入'切换为XX'或者输入生成题目数,XX为小学,初中,高中之一")View Code
分析:两个函数的参数都有Console主界面类,在逻辑上令人不解,另外登录和生成应当是系统功能的一部分,完全可以用一个类承载它们,而不必将它们单独放在外部
7.main()函数
def main(): """ 程序的主函数 整个文件从这里开始执行 """ main_login = Console() database = Library() while True: while not main_login.is_login(): # 未登录则进行登录操作 user_login(main_login, database) while main_login.is_login(): # 登录后就可以实现各种功能的调用 user_generate(main_login)View Code
分析:main()函数中不仅有界面类和用户库类的实例化,还用了一个while循环调用,while循环可以作为一个启动函数置于与user_login和user_generate函数同一个系统类中
四、算法分析
1.题目生成算法:用Block类中有content字符列表存储操作数,calculate存储操作符通过Regulation中的函数对calculate操作形成题目,basic_ge()函数随机出操作数与运算符,add_bracket()函数添加零或一个括号,通过生成0或1来随机,add_square()添加一个乘方,add_triangle()添加一个三角函数,最后调用combine()将calculate中的操作符与content中的操作数连接
分析:整体生成过程逻辑严谨,生成的步骤有迹可循,考虑到了平方三角函数使用括号明确计算顺序,过程还有is_square和is_triangle等状态变量为追踪题目生成提供了便利.
但是还有可提升的部分是能够调整计算顺序的括号至多生成一个,生成乘方和三角函数的数量在1-2间且两者概率没有区别
2.运行逻辑算法:在main函数中的while中从user_login函数开始要求登录,在其中接收输入,传入Console类对象中login再处理,登录后回到main函数调用user_generate函数接收输入后又进入Console类对象中经过generate_hw函数处理生成和change函数处理切换
分析:整体而言流程较为复杂且步骤间联系不强,主界面类对象作为了参数被函数user_login和user_generate反复调用.
优点是将过程拆解,细分了功能,提供了某种程度上的灵活性
五、测试样例分析
分析:各项功能能够正常使用,符合题目的要求,生成文件命名正确,内容正确,-1退出后能够再登录,输错有提示且能够重新输入
四、总结
整体结构较为复杂,功能被层层分解,具体功能实现逻辑严密,不易出错,界面符合题目要求,功能正常无错误
标签:结对,题目,函数,self,生成,互评,队友,user,列表 From: https://www.cnblogs.com/hujianfeng/p/17716471.html