作业基本信息
这个作业属于哪个课程 | < 2301-计算机学院-软件工程> |
---|---|
这个作业要求在哪里 | < 软工实践第一次作业> |
这个作业的目标 | < 实现一个简易计算器的功能,能有可视化的界面运行 > |
其他参考文献 | < Python_GUI教程(1) Python_GUI教程(2) |
解题思路描述
实现一个计算器的功能我是考虑由最简单的加减乘除入手,我选择了我稍有了解的python语言进行解题,我基于tkinter库进行界面化设计,用tkinter库自带的按钮(Button)、标签(Label)等类完成可视化界面。
计算功能我则是通过python自带的eval()函数以字符串拼接的形势进行运算,对不同的运算符进行判断从而实现各个的效果。
解题过程大概为 先实现简单加减乘除与初步的界面,完成后进一步设计三角函数等扩展功能,最后通过通过代码调试等进行改良优化。
Gitcode项目地址
效果展示
PSP表格
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 35 |
• Estimate | • 估计这个任务需要多少时间 | 15 | 25 |
Development | 开发 | 300 | 420 |
• Analysis | • 需求分析 (包括学习新技术) | 60 | 60 |
• Design Spec | • 生成设计文档 | 30 | 20 |
• Design Review | • 设计复审 | 30 | 25 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 20 | 20 |
• Design | • 具体设计 | 40 | 35 |
• Coding | • 具体编码 | 150 | 200 |
• Code Review | • 代码复审 | 50 | 30 |
• Test | • 测试(自我测试,修改代码,提交修改) | 60 | 150 |
Reporting | 报告 | 120 | 100 |
• Test Repor | • 测试报告 | 100 | 130 |
• Size Measurement | • 计算工作量 | 10 | 10 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 100 | 90 |
合计 | 1115 | 1350 |
接口设计和实现过程
1、创建并初始化计算器类Cal(),
- label1为缓存运算中的数字的标签(不显示在界面上)
- label2是实时显示运算过程的标签
- label3是显示运算结果的标签
- list是储存用于eval()函数运算的字符串表达式
- op_type储存字符串运算表达式中缺少右括号的数量
class Cal():
def __init__(self, init_window_name):
self.init_window_name = init_window_name
self.label1 = StringVar() # 缓存运算中的数字的标签(不显示)
self.label1.set('0')
self.label2 = StringVar() # 实时显示运算过程的标签
self.label2.set('result=0')
self.label3 = StringVar() # 显示运算结果的标签
self.label3.set('0')
self.list = [] # 储存需要运算的字符串表达式
self.op_type = 0 # 作为标记运算中括号的变量
2.input_num()函数是对数字按钮(0~9)进行处理,初始为0则进行替代,不为0则将已存的数字与新的数字进行拼接,同时实时更新显示标签
def input_num(self,num): # 对数字按钮的处理
temp = self.label3.get()
if temp == '0': # 对实时显示的标签进行更新
self.label3.set(num)
else:
self.label3.set(temp+num)
t_num = self.label1.get()
if t_num == '0': # 对缓存数字的标签进行更新
self.label1.set(num)
else:
newnum = t_num + num
self.label1.set(newnum)
3.对各类运算符进行处理(因为分支较多,此处仅展示部分)
def input_sign(self, sign):
num = self.label1.get()
temp = self.label3.get()
if temp == '0':
temp = ''
if self.op_type == 0:
if sign == 'Clear': # 实现归0,将所有变量恢复初值
self.list.clear()
self.label1.set('0')
self.label3.set('0')
self.op_type = 0
elif sign == 'sin': #实现三角函数中的san(),将字符串“sin(”拼接到list中储存,后面在eval()函数进行运算
if num != '0':
self.list.append(num)
self.list.append('sin(')
self.label3.set(temp+'sin(')
self.label1.set('0')
4.将储存起来的字符串表达式通过eval()函数进行运算,并利用try语句进行异常处理
def result(self):
t_num = self.label1.get()
if t_num!= '0':
self.list.append(t_num)
self.label1.set('0')
while self.op_type != 0: # 此循环目的在于将缺的右括号补上
self.list.append(')')
self.op_type -= 1
cal_Str = ''.join(self.list)
endNum = 0
try:
endNum = eval(cal_Str)
if isinstance(endNum, int):
self.label2.set("result={:.0f}".format(endNum))
else:
self.label2.set("result={:.02f}".format(endNum))
except (ZeroDivisionError):
self.label3.set("计算过程发生除零错误!")
except (SyntaxError):
self.label3.set("错误:缺少括号!")
except:
self.label3.set("计算过程发生错误!")
return endNum
关键代码展示
以下代码为input_sign()函数中运算符为±*/.的情况,通过对label1、label2、list、op_type四个变量的操作实现
else: # 运算符为+-*/.的情况
if num != '0': # label1中存缓存的数字不为0则就加到list中
self.list.append(num)
while self.op_type != 0: # 在'^'的按钮中会op_type会加1,表示表达式欠缺的')'数量,此处通过while循环补全,能够让表达式表示正确和实现次方的次方
self.list.append(')')
self.op_type -= 1
if sign == '*': # 对乘法运算显示的'*'替换为'X'
self.label3.set(temp+"X")
else:
self.label3.set(temp+sign)
self.list.append(sign)
self.label1.set('0')
性能改进与优化
- 一开始编写代码中在input_sign()函数里分为op_type是否为0的两种情况进行讨论(即表达式是否缺少右括号),但后续优化中发现两种情况的操作可以归为一类,无需用if语句进行选择判断
if self.op_type == 0:
if sign == 'Clear':
self.list.clear()
self.label1.set('0')
self.label2.set('result=0')
self.label3.set('0')
……
else:
if sign == 'Clear':
self.list.clear()
self.label1.set('0')
self.label2.set('result=0')
self.label3.set('0')
……
单元测试
本次使用python的unittest库进行单元测试,通过了五个测试函数进行测试,包括了加减乘除、次元、三角函数等混合操作进行了测试,结果如下:
单元测试代码的五个测试函数如下:
root = Tk()
class TestCalculator(unittest.TestCase):
def test1(self):
cal = Cal(root)
cal.list = "1+7*2+4/2"
r = cal.result()
self.assertEqual(r, 17)
def test2(self):
cal = Cal(root)
cal.list = "1024+0.99+2.5/2"
r = cal.result()
self.assertEqual(r, 1026.24)
def test3(self):
cal = Cal(root)
cal.list = "sin(pi/2)+1.44"
r = cal.result()
self.assertEqual(r, 2.44)
def test4(self):
cal = Cal(root)
cal.list = "1+sin(pi/2)+pow(2,2)"
r = cal.result()
self.assertEqual(r, 6)
def test5(self):
cal = Cal(root)
cal.list = "8*tan(pi/4)+pow(2,pow(2,2))"
r = cal.result()
self.assertEqual(r, 24.0)
if __name__ == '__main__':
unittest.main()
异常处理措施与展示
采用python的try操作进行异常处理
try:
endNum = eval(cal_Str)
if isinstance(endNum, int):
self.label2.set("result={:.0f}".format(endNum))
else:
self.label2.set("result={:.02f}".format(endNum))
except (ZeroDivisionError):
self.label3.set("计算过程发生除零错误!")
except (SyntaxError):
self.label3.set("错误:缺少括号!")
except:
self.label3.set("计算过程发生错误!")
- 异常处理展示
体会与总结
- 通过本次实现可视化计算器的过程,我大致感受到完成一个项目所需的流程 , 在开发前进行计划和需求分析,开发时编写代码、满足需求、设计文档,在开发后进行单元测试、bug调试等。
- 通过此次作业我处理需求和设计代码的能力有所提升,能更好的根据需求来编写、设计软件
- 基本熟悉了
Python
的tkinter
库的使用方式和流程,并能基于此完成简单的可视化软件编程。 - 不断编写代码过程中提高了我debug的能力,处理bug比起以前能更加从容。
- 学会了如何进行单元测试和异常处理,对于改进和优化程序的了解更进一步。