这个作业属于哪个课程 | https://bbs.csdn.net/forums/ssynkqtd-05 |
---|---|
这个作业要求在哪里 | https://bbs.csdn.net/topics/617294583 |
这个作业的目标 | 实现一个具有可视化界面的科学计算器 |
参考文献 | http://t.csdnimg.cn/4Qomr |
目录
Github项目地址
https://github.com/BillOzmov/102101520-softwarework
功能展示
计算器展示
PSP表格
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 25 | 20 |
• Estimate | • 估计这个任务需要多少时间 | 25 | 20 |
Development | 开发 | 975 | 1200 |
• Analysis | • 需求分析 (包括学习新技术) | 300 | 300 |
• Design Spec | • 生成设计文档 | 60 | 40 |
• Design Review | • 设计复审 | 30 | 20 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 30 | 10 |
• Design | • 具体设计 | 90 | 90 |
• Coding | • 具体编码 | 300 | 300 |
• Code Review | • 代码复审 | 45 | 70 |
• Test | • 测试(自我测试,修改代码,提交修改) | 120 | 370 |
Reporting | 报告 | 100 | 140 |
• Test Repor | • 测试报告 | 30 | 60 |
• Size Measurement | • 计算工作量 | 20 | 30 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 50 | 50 |
合计 | 1100 | 1360 |
解题思路描述
1.如何实现计算器的GUI界面:
我选择了Python的tkinter库进行计算器的界面开发,因为Python提供了丰富的模块和库,可以方便地扩展GUI应用程序的功能,而且tkinter库的使用较为简便。
2.如何实现计算器的核心功能:
显示计算界面:
我使用tk.StringVar() 创建一个字符串变量来表示计算结果通过tk.label()显示在计算器界面上,按钮则通过在一个buttons列表中遍历显示出来。
按钮功能:
在点击计算器按钮后应根据按钮文本内容的不同进行不同的操作,因此我将按钮文本内容作为参数传入同一个函数,在这个函数中进行字符串匹配再执行相应的操作。
计算表达式:
主要利用self.result_var.get()获取字符串的特定部分再将其转化为数字或字符进行运算。
3.exe打包:
选择安装pyinstaller对py文件进行打包。
设计与实现过程
1.将所有按钮装入一个列表,命令是调用函数传入对应文本字符串:
buttons = [
("arctan", "sin", "cos", "tan", "x!", "1/x"),
("arcsin", "^", "√", "%", "(", ")"),
("arccos", "7", "8", "9", "÷", "AC"),
("lg" , "4", "5", "6", "*", "CE"),
("ln" , "1", "2", "3", "-", "←"),
("π" , "e", "0", ".", "+", "=")
]
for i, row in enumerate(buttons):
for j, button_text in enumerate(row):
button = tk.Button(button_frame, text=button_text, font=("Arial", 18), command=lambda text=button_text: self.on_button_click(text))
2.根据字符串匹配操作
def on_button_click(self, button_text):
self.num = self.digits()
if self.num!=0:
...
if button_text == ".":
if self.poi == 0: #仅在当前数无小数点时按钮生效
...
elif button_text in "πe":
...
elif button_text in self.operator_hyper:
...
elif ...
else:
self.All_Clear() #都匹配不上就AC
3.实现跟删除有关的功能。一开始觉得删除很简单,切片掉最后一个字符就行,后来做科学计算器的时候三角函数对数函数的字符都不是单个的,对实现删除功能的函数进行了一次大改。全清完设置显示默认的“0”。
AC:本来只是把字符串设为0就结束了,后面随着测试中的不断报错,也扩展成了一个函数
CE:删掉当前的数,本来是在字符串中从后往前循环删除数字,发现这样很容易触发越界错误后,新增一个检测当前数位、小数点、操作符的digits()函数,
"←":
def delete(self):
strlen = len(self.result_var.get())
if self.result_var.get() == "错误":
self.All_Clear()
return
if self.result_var.get()[-1] == " ": # 如果有空格把空格去掉
self.result_var.set(self.result_var.get()[0:-1])
if self.result_var.get()[0].isdigit() and strlen <= 1: #如果被删完了就保留一个“0”的显示,结束delete执行
self.All_Clear()
return
if self.result_var.get()[-1].isalpha(): #用来删不止一个字符表示的操作符
...
elif self.result_var.get()[-1] in "+-*÷%":
self.result_var.set(self.result_var.get()[0:-1])
self.op=0
else:
self.result_var.set(self.result_var.get()[0:-1])
4.计算。在实现简易计算器的时候只有在点击“=”号后才会运算,实现科学计算器的时候为了实现高级操作符的运算增加了opmanifest()函数:
def opmanifest(self,opr):
if opr =="1/x":
self.result_var.set(self.result_var.get() + " " + "^(-1)" + " ")
elif opr =="x!":
if self.poi >= 1 or self.op>=1: #有小数点说明不是整数,不能阶乘; 操作符不能被阶乘;
self.errormanage()
else:
...
if self.hyper == 0:
if opr == "NULL":
return
else:
for i in range(0, len(self.operator_hyper)):
if opr == self.operator_hyper[i]:
self.hyper = i
return
elif self.hyper>2:
...
关键代码展示
上文已介绍计算、删除等功能的实现,此处展示实现的计算器类框架:
class Calculator(tk.Tk):
def __init__(self):
super().__init__()
self.title("科学计算器")
self.geometry("400x400")
self.create_widgets()
self.num=0 #标记当前数位
self.temp=0 #标记括号数,左括号加1,右括号减1
self.poi=0 #标记当前数字具有小数点的个数
self.op=0 #标记当前连续的操作符个数
self.hyper=0 #标记是否有左位高级操作符还没结算,若使用了则赋值为操作符在列表中的对应下标,追踪下一个操作数 直至下个操作符出现
self.hlen=0 #标记使用高级运算符前的字符串长度
self.numb=0 #存当前数字
self.operator_hyper = ['1/x', 'x!', '^', 'lg', 'ln', '√', 'sin', 'cos', 'tan', 'arcsin', 'arccos', 'arctan']
def create_widgets(self):
self.result_var = tk.StringVar()
self.result_var.set("0")
result_label = tk.Label(self, textvariable=self.result_var, anchor="e", font=("Arial", 24), bg="white", bd=10)
result_label.pack(fill=tk.BOTH, expand=True)
button_frame = tk.Frame(self)
button_frame.pack(fill=tk.BOTH, expand=True)
buttons = [
("arctan", "sin", "cos", "tan", "x!", "1/x"),
("arcsin", "^", "√", "%", "(", ")"),
("arccos", "7", "8", "9", "÷", "AC"),
("lg" , "4", "5", "6", "*", "CE"),
("ln" , "1", "2", "3", "-", "←"),
("π" , "e", "0", ".", "+", "=")
]
for i, row in enumerate(buttons):
for j, button_text in enumerate(row):
button = tk.Button(button_frame, text=button_text, font=("Arial", 18), command=lambda text=button_text: self.on_button_click(text))
if i<3 and j<1:
button.config(bg="#fecc11")
... #省略部分按钮样式设计
button.grid(row=i, column=j, sticky="news")
for i in range(6):
button_frame.rowconfigure(i, weight=1)
button_frame.columnconfigure(i, weight=1)
性能改进
计算逻辑改进:
由于一开始采用eval()对字符串进行计算,实现科学计算功能时若只在按下”=“号后进行计算容易出现Syntax Error语法错误等问题。
故之后增加的opmanifest()函数在往字符串中加入新的操作符时会对上一个高级操作符进行结算,如:
当前算式:1 * sin π
按下加号后:1 * 0.0 +
异常处理
多个小数点在button"."做了限制,只有在当前数没有小数点的情况下才生效;
针对括号匹配、多个操作符的问题设计了errormanage()函数,触发后撤回异常操作:
def errormanage(self):
if self.temp<0:
messagebox.showwarning("错误(括号匹配)", "若如此做括号将无法匹配,故将此右括号删去")
while self.temp<0:
if self.result_var.get()[-1] == ")":
self.temp += 1
elif self.result_var.get()[-1] == " " and self.result_var.get()[-2] == ")":
self.temp += 1
self.delete()
elif self.op>=2:
messagebox.showwarning("错误(操作符)", "操作符不能连续出现两次,故将此操作符删去")
self.delete()
self.op-=1
单元测试
import unittest
from tkinter import Tk
from calculator_science import Calculator
class TestCalculator(unittest.TestCase):
def setUp(self):
self.root = Tk()
self.calculator = Calculator()
def tearDown(self):
self.root.destroy()
def test_addition(self):
# 测试加法运算
self.calculator.Click('2')
self.calculator.Click('+')
self.calculator.Click('3')
self.calculator.Click('=')
self.assertEqual(self.calculator.result_var.get(), '5')
def test_subtraction(self):
# 测试减法运算
self.calculator.Click('2')
self.calculator.Click('-')
self.calculator.Click('3')
self.calculator.Click('=')
self.assertEqual(self.calculator.result_var.get(), '-1')
def test_multiplication(self):
# 测试乘法运算
self.calculator.Click('22')
self.calculator.Click('*')
self.calculator.Click('30')
self.calculator.Click('=')
self.assertEqual(self.calculator.result_var.get(), '660')
def test_division(self):
# 测试除法运算
self.calculator.Click('21')
self.calculator.Click('÷')
self.calculator.Click('30')
self.calculator.Click('=')
self.assertEqual(self.calculator.result_var.get(), '0.7')
def test_power(self):
# 测试幂运算
self.calculator.Click('2')
self.calculator.Click('^')
self.calculator.Click('10')
self.calculator.Click('=')
self.assertEqual(self.calculator.result_var.get(), '1024')
def test_logarithm(self):
# 测试对数函数
self.calculator.Click('3')
self.calculator.Click('lg')
self.calculator.Click('100')
self.calculator.Click('=')
self.assertEqual(self.calculator.result_var.get(), '6.0')
def test_square_root(self):
# 测试平方根函数
self.calculator.Click('7')
self.calculator.Click('√')
self.calculator.Click('9')
self.calculator.Click('=')
self.assertEqual(self.calculator.result_var.get(), '21.0')
def test_error_divide_by_zero(self):
# 测试除以零的错误
self.calculator.Click('7')
self.calculator.Click('÷')
self.calculator.Click('0')
self.calculator.Click('=')
self.assertEqual(self.calculator.result_var.get(), '错误')
def test_triangle_functions(self):
# 测试三角函数
self.calculator.Click('sin')
self.calculator.Click('3')
self.calculator.Click('+')
self.calculator.Click('1')
self.calculator.Click('=')
self.assertEqual(self.calculator.result_var.get(), '1.1411')
def test_pi(self):
# 测试π
self.calculator.Click('2')
self.calculator.Click('π')
self.calculator.Click('=')
self.assertEqual(self.calculator.result_var.get(), '6.2832')
def test_e(self):
# 测试e
self.calculator.Click('e')
self.calculator.Click('=')
self.assertEqual(self.calculator.result_var.get(), '2.7183')
def test_All_clear(self):
# 测试清除功能
self.calculator.Click('7')
self.calculator.Click('arcsin')
self.calculator.Click('π')
self.calculator.All_Clear()
self.assertEqual(self.calculator.result_var.get(), '0')
if __name__ == '__main__':
unittest.main()
测试结果
心得体会和经验总结
我的收获主要是通过这次作业我学习了如何通过Python实现具有gui界面的计算器,py文件如何打包成exe文件。
但总的来说计划的还是不够充分,属于是前期省力后期费力。
GUI界面制作方面:选择用Python自带的thinter库做GUI界面就是成型容易美化难;
计算器核心功能方面:选择的计算方式不大合适,用eval()算加减乘除的时候很容易,到三角函数对数函数这些先载入操作符且无法直接识别或直接替换的就比较麻烦了,后面为了解决问题加了不少判断语句,现在想想如果用堆栈来做计算实现高级运算符的功能会更方便。