这个作业属于哪个课程 | <班级的链接> |
---|---|
这个作业要求在哪里 | https://bbs.csdn.net/forums/ssynkqtd-05?spm=1001.2014.3001.6685 |
这个作业的目标 | 完成一个具有可视化界面的计算器 |
其他参考文献 | tkinter模块的帮助文档 |
目录:
0.项目基本功能展示
1. Gitcode项目地址
2. PSP表格
3. 解题思路描述
4. 接口设计和实现过程
5. 关键代码展示
6. 性能改进
7. 单元测试
8. 心得体会
0.项目基本功能展示
1. Gitcode项目地址
GitHub - lff102101510/my_homework-1.0: 第一次软件工程作业
2. PSP表格
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 15 | 5 |
• Estimate | • 估计这个任务需要多少时间 | 15 | 15 |
Development | 开发 | 10 | 15 |
• Analysis | • 需求分析 (包括学习新技术) | 20 | 15 |
• Design Spec | • 生成设计文档 | 30 | 30 |
• Design Review | • 设计复审 | 10 | 10 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
• Design | • 具体设计 | 20 | 20 |
• Coding | • 具体编码 | 120 | 180 |
• Code Review | • 代码复审 | 20 | 30 |
• Test | • 测试(自我测试,修改代码,提交修改) | 60 | 60 |
Reporting | 报告 | 20 | 10 |
• Test Repor | • 测试报告 | 30 | 30 |
• Size Measurement | • 计算工作量 | 15 | 5 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 25 | 10 |
合计 | 420 | 445 |
3. 解题思路描述
这次作业的要求是:使用任意语言,开发一个可视化的科学计算器。刚好Python语言中的Tkinter模块可以实现开发可视化GUI界面,再加上python有个bug级别的计算值语句“eval()"。所以我选择了python3作为语言进行开发。界面设计方面,我模仿了win10系统自带的科学计算器的布局。
我定义了一个类,里面包括了前端界面的书写和后端计算机性能具体的函数实现。
前端方面,使用tkinter模块中的Tk()创建计算器的窗口。使用tkinter模块中的Button
在界面上整齐地安放按钮并进行按钮样式的设计。
后端方面,我在”calculator“类中定义了多个函数,如”equal""addchar"来进行具体的计算。
作业要求提交exe文件和源代码,使用pyinstaller进行.exe文件的生成。
4. 接口设计和实现过程
#使用Entry()函数创建输入框
#使用self.string变量承接输入框的输入内容,以字符串形式记录
entry = Entry(window, textvariable=self.string,width = 30,font = custom_font)
self.string = StringVar()
#在这里将输入框内输入的内容转化成“eval()”函数可以直接理解的内容
#这里我专门列出了‘sin’‘cos’等无法直接被eval函数的特殊情况
#直接在这一步调用math模块的函数把他们的结果记录在self.string中以便输出
def addChar(self, char):
if char == "sin":
try:
angle = float(self.string.get())
self.value = math.sin(math.radians(angle))
self.string.set(self.value)
except ValueError:
self.string.set("无效输入")
elif char == "cos":
try:
angle = float(self.string.get())
self.value = math.cos(math.radians(angle))
self.string.set(self.value)
except ValueError:
self.string.set("无效输入")
elif char == "tan":
try:#这里可能存在精度问题
angle = float(self.string.get())
self.value = math.tan(math.radians(angle))
self.string.set(self.value)
except ValueError:
self.string.set("无效输入")
elif char == "log":
try:
angle = float(self.string.get())
self.value = math.log(angle,10)
self.string.set(self.value)
except ValueError:
self.string.set("无效输入")
elif char == "e":
try:
self.string.set(2.7182818284590)
except ValueError:
self.string.set("无效输入")
elif char == "pi":
try:
self.string.set(3.1415926535)
except ValueError:
self.string.set("无效输入")
elif char == "sqrt":
try:
angle = float(self.string.get())
self.value = math.sqrt(angle)
self.string.set(self.value)
except ValueError:
self.string.set("无效输入")
elif char == "pow":
try:
angle = float(self.string.get())
self.value = math.pow(angle,2)
self.string.set(self.value)
except ValueError:
self.string.set("无效输入")
else:
self.string.set(self.string.get() + str(char))
#按下等号后,执行这个函数进行最终的计算
def equals(self):
result = ""
try:
result = eval(self.string.get())
self.string.set(result)
except:
result = "无效输入"
#这一句意思是将result添加到最后要输出的部分中
self.string.set(result)
self.value = 0
5. 关键代码展示
上面展示了基本接口和功能的代码,这里给大家看看界面设置前端部分的代码吧。
def __init__(self):
window = Tk()
window.title('您的科学计算器')
window.configure(background="white")
self.string = StringVar()
self.value = 0
font_size = 16
font_weight = "bold"
custom_font = font.Font(size = font_size,weight = font_weight)
entry = Entry(window, textvariable=self.string,width = 30,font = custom_font)
entry.grid(row=0, column=0,columnspan = 6,sticky = "NSEW")
entry.configure(background="white")
entry.focus()
window.grid_rowconfigure(0,weight = 1)
window.grid_columnconfigure(0,weight =1)
values = ["(", ")", "%","C", "DEL",
"sqrt", "pow","e","pi", "/",
"cos", "7", "8", "9", "*",
"tan", "4", "5", "6", "-",
"sin","1", "2", "3", "+",
"log", "0", ".", "="]
text = 1
i = 0
row = 1
col = 0
for txt in values:
padx = 15
pady = 15
if (i == 5):
row = 2
col = 0
if (i == 10):
row = 3
col = 0
if (i == 15):
row = 4
col = 0
if (i == 20):
row = 5
col = 0
if (i == 25):
row = 6
col = 0
if (txt == '='):
style = Style()
style.configure('Tbutton',borderwidth = 0,bodercolor = "Orange",relief = 'flat',borderradius = 100)
btn = Button(window, height=2, width=4, padx=48, pady=5, text=txt,
command=lambda txt=txt: self.equals(),font = ("Arial",16),bd =2,highlightthickness = 2)
btn.config(highlightcolor = "grey")
btn.grid(row=row, column=col, columnspan=3, padx=0, pady=0)
btn.configure(background="Orange")
elif (txt == 'DEL'):
btn = Button(window, height=2, width=4, padx=10, pady=5, text=txt,
command=lambda txt=txt: self.delete(),font = ("Arial",16),bd = 2,highlightthickness = 2)
btn.config(highlightcolor="grey")
btn.grid(row=row, column=col, padx=0, pady=0)
btn.configure(background="grey")
elif (txt == 'C'):
btn = Button(window, height=2, width=4, padx=10, pady=5, text=txt,
command=lambda txt=txt: self.clearall(),font = ("Arial",16),bd = 2,highlightthickness = 2)
btn.config(highlightcolor="grey")
btn.grid(row=row, column=col, padx=0, pady=0)
btn.configure(background="grey")
elif(txt == '7' or txt =='8' or txt == '9' or txt == '4' or txt == '5' or txt == '6'or txt == '1' or txt == '2' or txt == '3' or txt == 'abs' or txt == '0' or txt == '.'):
btn = Button(window, height=2, width=4, padx=10, pady=5, text=txt,
command=lambda txt=txt: self.addChar(txt),font = ("Arial",16),bd = 2,highlightthickness = 2)
btn.config(highlightcolor = "grey")
btn.grid(row=row, column=col, padx=0, pady=0)
btn.configure(background="Snow")
#加减乘除已经OK了
elif( txt == '+' or txt == '-' or txt == '*' or txt =='/'):
style = Style()
style.configure('Tbutton', borderwidth=0, bodercolor="Orange", relief='flat', borderradius=100)
btn = Button(window, height=2, width=4, padx=10, pady=5, text=txt,
command=lambda txt=txt: self.addChar(txt), font=("Arial", 16), bd=2, highlightthickness=2)
btn.config(highlightcolor="grey")
btn.grid(row=row, column=col, columnspan=3, padx=0, pady=0)
btn.configure(background="Orange")
else:
btn = Button(window, height=2, width=4, padx=10, pady=5, text=txt,
command=lambda txt=txt: self.addChar(txt),font = ("Arial",16),bd = 2,highlightthickness = 2)
btn.config(highlightcolor = "grey")
btn.grid(row=row, column=col, padx=0, pady=0)
btn.configure(background="LightGrey")
col = col + 1
i = i + 1
window.mainloop()
6. 性能改进
在实现拓展功能“实现‘sin’‘cos’等函数”的时候,发现一开始的处理方式无法适配这种情况。一调用‘sin’或者‘cos’函数,就显示“无效输入”。
self.string.set(self.string.get() + str(char))
后来发现,出现这种情况和“eval()”函数的特性和我设置的异常处理机制有关系。在这里我使用“eval()”函数将 addchar()函数处理好的,直接是数学公式的字符串进行计算并求职,若字符串无法被“eval()”函数识别,就启动异常捕获机制,将输出内容变为“无效输入”。
问题在于,如果我们在输入框按自然数学的习惯,键入”sin30",则会出现两个问题。
一,eval()函数处理不了这个公式,只能抛出异常,异常被捕获之后便输出“无效输入”。
二,python中处理三角函数相关的语句应该是:
math.sin(degree)
def equals(self):
result = ""
try:
result = eval(self.string.get())
self.string.set(result)
except:
result = "无效输入"
self.string.set(result)
self.value = 0
将代码改成如下格式之后成功解决。其他的调用python math库函数计算的功能也一并这么做。
if char == "sin":
try:
angle = float(self.string.get())
self.value = math.sin(math.radians(angle))
self.string.set(self.value)
except ValueError:
self.string.set("无效输入")
elif char == "cos":
try:
angle = float(self.string.get())
self.value = math.cos(math.radians(angle))
self.string.set(self.value)
except ValueError:
self.string.set("无效输入")
elif char == "tan":
try:#这里可能存在精度问题
angle = float(self.string.get())
self.value = math.tan(math.radians(angle))
self.string.set(self.value)
except ValueError:
self.string.set("无效输入")
elif char == "log":
try:
angle = float(self.string.get())
self.value = math.log(angle,10)
self.string.set(self.value)
except ValueError:
self.string.set("无效输入")
elif char == "e":
try:
self.string.set(2.7182818284590)
except ValueError:
self.string.set("无效输入")
elif char == "pi":
try:
self.string.set(3.1415926535)
except ValueError:
self.string.set("无效输入")
elif char == "sqrt":
try:
angle = float(self.string.get())
self.value = math.sqrt(angle)
self.string.set(self.value)
except ValueError:
self.string.set("无效输入")
elif char == "pow":
try:
angle = float(self.string.get())
self.value = math.pow(angle,2)
self.string.set(self.value)
except ValueError:
self.string.set("无效输入")
else:
self.string.set(self.string.get() + str(char))
7. 单元测试
用python跑测试也太麻烦了!妈呀,测试代码比原来的代码还多^ _ ^,话不多说,直接上代码!
import unittest
from tkinter import Tk,font
from tkinter import StringVar, Entry, Button
from tkinter.ttk import Style
import math
class calculator:
def __init__(self):
window = Tk()
window.title('您的科学计算器')
window.configure(background="white")
self.string = StringVar()
self.value = 0
font_size = 16
font_weight = "bold"
custom_font = font.Font(size = font_size,weight = font_weight)
entry = Entry(window, textvariable=self.string,width = 30,font = custom_font)
entry.grid(row=0, column=0,columnspan = 6,sticky = "NSEW")
entry.configure(background="white")
entry.focus()
window.grid_rowconfigure(0,weight = 1)
window.grid_columnconfigure(0,weight =1)
values = ["(", ")", "%","C", "DEL",
"sqrt", "pow","e","pi", "/",
"cos", "7", "8", "9", "*",
"tan", "4", "5", "6", "-",
"sin","1", "2", "3", "+",
"log", "0", ".", "="]
text = 1
i = 0
row = 1
col = 0
for txt in values:
padx = 15
pady = 15
if (i == 5):
row = 2
col = 0
if (i == 10):
row = 3
col = 0
if (i == 15):
row = 4
col = 0
if (i == 20):
row = 5
col = 0
if (i == 25):
row = 6
col = 0
if (txt == '='):
style = Style()
style.configure('Tbutton',borderwidth = 0,bodercolor = "Orange",relief = 'flat',borderradius = 100)
btn = Button(window, height=2, width=4, padx=48, pady=5, text=txt,
command=lambda txt=txt: self.equals(),font = ("Arial",16),bd =2,highlightthickness = 2)
btn.config(highlightcolor = "grey")
btn.grid(row=row, column=col, columnspan=3, padx=0, pady=0)
btn.configure(background="Orange")
elif (txt == 'DEL'):
btn = Button(window, height=2, width=4, padx=10, pady=5, text=txt,
command=lambda txt=txt: self.delete(),font = ("Arial",16),bd = 2,highlightthickness = 2)
btn.config(highlightcolor="grey")
btn.grid(row=row, column=col, padx=0, pady=0)
btn.configure(background="grey")
elif (txt == 'C'):
btn = Button(window, height=2, width=4, padx=10, pady=5, text=txt,
command=lambda txt=txt: self.clearall(),font = ("Arial",16),bd = 2,highlightthickness = 2)
btn.config(highlightcolor="grey")
btn.grid(row=row, column=col, padx=0, pady=0)
btn.configure(background="grey")
elif(txt == '7' or txt =='8' or txt == '9' or txt == '4' or txt == '5' or txt == '6'or txt == '1' or txt == '2' or txt == '3' or txt == 'abs' or txt == '0' or txt == '.'):
btn = Button(window, height=2, width=4, padx=10, pady=5, text=txt,
command=lambda txt=txt: self.addChar(txt),font = ("Arial",16),bd = 2,highlightthickness = 2)
btn.config(highlightcolor = "grey")
btn.grid(row=row, column=col, padx=0, pady=0)
btn.configure(background="Snow")
#加减乘除已经OK了
elif( txt == '+' or txt == '-' or txt == '*' or txt =='/'):
style = Style()
style.configure('Tbutton', borderwidth=0, bodercolor="Orange", relief='flat', borderradius=100)
btn = Button(window, height=2, width=4, padx=10, pady=5, text=txt,
command=lambda txt=txt: self.addChar(txt), font=("Arial", 16), bd=2, highlightthickness=2)
btn.config(highlightcolor="grey")
btn.grid(row=row, column=col, columnspan=3, padx=0, pady=0)
btn.configure(background="Orange")
else:
btn = Button(window, height=2, width=4, padx=10, pady=5, text=txt,
command=lambda txt=txt: self.addChar(txt),font = ("Arial",16),bd = 2,highlightthickness = 2)
btn.config(highlightcolor = "grey")
btn.grid(row=row, column=col, padx=0, pady=0)
btn.configure(background="LightGrey")
col = col + 1
i = i + 1
window.mainloop()
def clearall(self):
self.string.set("")
def equals(self):
result = ""
try:
result = eval(self.string.get())
self.string.set(result)
except:
result = "无效输入"
self.string.set(result)
self.value = 0
def addChar(self, char):
if char == "sin":
try:
angle = float(self.string.get())
self.value = math.sin(math.radians(angle))
self.string.set(self.value)
except ValueError:
self.string.set("无效输入")
elif char == "cos":
try:
angle = float(self.string.get())
self.value = math.cos(math.radians(angle))
self.string.set(self.value)
except ValueError:
self.string.set("无效输入")
elif char == "tan":
try:#这里可能存在精度问题
angle = float(self.string.get())
self.value = math.tan(math.radians(angle))
self.string.set(self.value)
except ValueError:
self.string.set("无效输入")
elif char == "log":
try:
angle = float(self.string.get())
self.value = math.log(angle,10)
self.string.set(self.value)
except ValueError:
self.string.set("无效输入")
elif char == "e":
try:
self.string.set(2.7182818284590)
except ValueError:
self.string.set("无效输入")
elif char == "pi":
try:
self.string.set(3.1415926535)
except ValueError:
self.string.set("无效输入")
elif char == "sqrt":
try:
angle = float(self.string.get())
self.value = math.sqrt(angle)
self.string.set(self.value)
except ValueError:
self.string.set("无效输入")
elif char == "pow":
try:
angle = float(self.string.get())
self.value = math.pow(angle,2)
self.string.set(self.value)
except ValueError:
self.string.set("无效输入")
else:
self.string.set(self.string.get() + str(char))
def delete(self):
self.string.set(self.string.get()[0:-1])
class TestCalculator(unittest.TestCase):
def setUp(self):
self.window = Tk()
self.calculator_instance = calculator()
#测试加
def test_addition(self):
self.calculator_instance.addChar('2')
self.calculator_instance.addChar('+')
self.calculator_instance.addChar('3')
self.calculator_instance.equals()
self.assertEqual(self.calculator_instance.string.get(), '5')
#测试减
def test_subtraction(self):
self.calculator_instance.addChar('5')
self.calculator_instance.addChar('-')
self.calculator_instance.addChar('3')
self.calculator_instance.equals()
self.assertEqual(self.calculator_instance.string.get(), '2')
#测试乘
def test_multiply(self):
self.calculator_instance.addChar('5')
self.calculator_instance.addChar('*')
self.calculator_instance.addChar('3')
self.calculator_instance.equals()
self.assertEqual(self.calculator_instance.string.get(), '15')
# 测试除
def test_divide(self):
self.calculator_instance.addChar('5')
self.calculator_instance.addChar('/')
self.calculator_instance.addChar('0')
self.calculator_instance.equals()
self.assertEqual(self.calculator_instance.string.get(), '无效输入')
#测试sin cos tan
def test_sin(self):
self.calculator_instance.addChar('30')
self.calculator_instance.addChar('sin')
self.calculator_instance.equals()
self.assertEqual(self.calculator_instance.string.get(), '0.49999999999999994')
def test_cos(self):
self.calculator_instance.addChar('60')
self.calculator_instance.addChar('cos')
self.calculator_instance.equals()
self.assertEqual(self.calculator_instance.string.get(), '0.5000000000000001')
def test_tan(self):
self.calculator_instance.addChar('0.9999999999999999')
self.calculator_instance.addChar('tan')
self.calculator_instance.equals()
self.assertEqual(self.calculator_instance.string.get(), '1')
#测试pow和log
def test_pow(self):
self.calculator_instance.addChar('2')
self.calculator_instance.addChar('pow')
self.calculator_instance.equals()
self.assertEqual(self.calculator_instance.string.get(), '4.0')
def test_log(self):
self.calculator_instance.addChar('10')
self.calculator_instance.addChar('log')
self.calculator_instance.equals()
self.assertEqual(self.calculator_instance.string.get(), '1.0')
if __name__ == '__main__':
unittest.main()
九个基本功能测试通过!
8. 异常处理
出现异常的话,就使用except捕获异常,输出“无效输入”。
9.心得体会
我将在两个方面阐述我的心得体会。
在具体功能实现方面,我在实践中认识到了“eval”函数的局限性,即使是万能的python库函数也有它的限制,要充分了解它的特性才能得以利用。
在界面设计方面,我感觉我的想法还没有完全实现。本来想把每个按钮的边角做成圆角,再把数字键设置成透明的,哈哈,利用Button生成按钮好像不能设计按钮的这一类样式呢。
下次试试前后端分开!使用css ➕ html能对界面进行更多的操作!
PS:用sin30,cos30,tan45去测试会出现“0.499999999999”,“0.50000000001”,“0.9999999999”,应该是把输入的角度值,转化成弧度值,再对输出值转化为float型这一连串操作产生的一些精度丢失。