• Python tkinter 实现带括号功能的计算器


    一. 中缀表达式与后缀表达式:

    1. 简介

    中缀表达式的特征是运算符的位置在两个参数之间,例如“13+14×10”、“18÷5+12”。一看便知,中缀表达式就是自然运算式,只不过现在为了有别于后缀表达式,另起了一个名字。

    2. 转换

    现在来讲解一下如何将中缀表达式转换成后缀表达式。在 Python 代码中可以创建两个空列表,一个用来临时存储运算符,另一个用来存储后缀表达式的列表形式。比如对于中缀表达式“13+14×10”,遍历它,首先将 13 存到后缀表达式列表中索引 0 的位置;第二次循环将 ‘+’ 存到运算符列表中;第三次把 14 存到后缀表达式中,索引为1;接下来遇到‘×’,将它与已经存在于运算符列表的 ‘+’ 进行比较,因为数学运算规则中乘法优先级高于加法,所以将 ‘×’ 存到运算符列表 ‘+’ 之后的位置(反之,如果是先遇到 ‘×’ 后遇到 ‘+’,则将 ‘×’ 弹出并追加到后缀表达式列表的最后位置);第五次遍历,将 10 存到后缀表达式列表索引 2 的位置。中缀表达式已经遍历完成,下面将运算符列表中的元素倒序追加到后缀表达式列表末尾,最终生成的后缀表达式列表就是 [13,14,10,'×','+']。

    中缀表达式遍历元素运算符列表后缀表达式列表
    13[][13]
    '+'['+'][13]
    14['+'][13,14]
    '×'['+','×'][13,14]
    10['+','×'][13,14,10]
    已遍历完['+','×'][13,14,10,'×','+']

    3. 运算

    接下来讲一下后缀表达式的运算步骤。需要用到一个临时的栈。在这个项目里,只需实现一个简单的栈即可。还是用上边这个例子,遍历这个列表,如果遇到数字,就压入栈;如果遇到运算符,就把栈顶的两个数字弹出,执行相应的运算,将运算结果再压入栈;判断当后缀表达式列表长度为0时结束循环,如果这时栈中只有一个元素,弹出这个元素并打印,这个元素就是运算结果。

    后缀表达式列表
    [13,14,10,'×','+'][]
    [14,10,'×','+'][13]
    [10,'×','+'][14,13]
    ['×','+'][10,14,13]
    ['+'][140,13]
    [][153]

    4. 有括号的情况

    括号的运算优先级是最高的,所以要在转换后缀表达式的代码中加一点逻辑。在遍历中缀表达式时如果遇到右括号,就在运算符列表里向前找到左括号的索引值,将从这个索引值加一对应的元素到列表末尾的元素从列表中弹出,追加到后缀表达式列表中,左括号则只弹出,不追加到后缀表达式列表中。

    二. 计算器的具体实现:

    1. 创建一个计算器类:

    初始化时只需要定义计算器实例的表达式这一个变量。

    1. def __init__(self, exp):
    2. self.exp = exp

    2. 格式检查:

    这个步骤指的是在用户输入了表达式之后,程序会对表达式的逻辑是否正确进行判断,比如连续输入了两个运算符。

    1. def preCheck(self):
    2. exp = self.exp
    3. for index in range(len(exp)):
    4. if exp[index] in "+-×÷" and exp[index - 1] in "+-×÷":
    5. return False
    6. if exp[index] == "." and exp[index - 1] == ".":
    7. return False
    8. return self.exp

    3. 格式改造:

    这一步的作用是将用户输入的字符串改造成列表,这样可以使数字和运算符独立存在,方便后续操作。

    1. def formatChange(self):
    2. self.midfix = []
    3. tmp = ''
    4. for index in range(len(self.exp)):
    5. // 如果第一个字符是负号“-”
    6. if index == 0 and self.exp[index] == "-":
    7. tmp += "-"
    8. // 如果字符是负号“-”且前一个字符是左括号
    9. elif index > 0 and self.exp[index - 1] == "(" and self.exp[index] == "-":
    10. tmp += str(self.exp[index])
    11. // 如果字符是数字或小数点或“π”
    12. elif self.exp[index].isdigit() or self.exp[index] == '.' or self.exp[index] == 'π':
    13. tmp += str(self.exp[index])
    14. else:
    15. if tmp != '':
    16. self.midfix.append(tmp)
    17. tmp = ''
    18. self.midfix.append(self.exp[index])
    19. if tmp != '':
    20. self.midfix.append(tmp)
    21. return self.midfix

    4. 转换后缀表达式:

    1. def makeSuffixExp(self):
    2. opList = []
    3. opDict = self.opDict
    4. self.suffixExpList = []
    5. for item in self.midfix:
    6. // 如果是数
    7. if self.isNumber(item):
    8. self.suffixExpList.append(item)
    9. // 如果是π
    10. if item == 'π':
    11. self.suffixExpList.append(str(math.pi))
    12. // 如果是这几种运算符号(+-×÷^!)
    13. if item in "+-×÷^!":
    14. if opList == []:
    15. opList.append(item)
    16. else:
    17. if opDict[item] <= opDict[opList[len(opList)-1]]:
    18. for index in range(len(opList)):
    19. self.suffixExpList.append(opList[index])
    20. del opList[index]
    21. opList.append(item)
    22. // 如果是左括号
    23. if item == "(":
    24. opList.append(item)
    25. // 如果是右括号
    26. if item == ")":
    27. pt = len(opList) - 1
    28. while opList[pt] != "(":
    29. pt -= 1
    30. pt += 1
    31. for index in range(pt, len(opList)):
    32. self.suffixExpList.append(opList[index])
    33. opList.pop(index)
    34. opList.pop()
    35. self.suffixExpList.extend(opList[::-1])
    36. return self.suffixExpList

    其中,opDict 是我定义的一个字典,定义了运算符的优先级:

    opDict = {"+":1, "-":1, "×":2, "÷":2, "(":0, "^":3, "!":3}

    这里也引用了我自定义的一个函数 isNumber,用来判断一个变量是否是数字:

    1. def isNumber(self, num):
    2. try:
    3. float(num)
    4. return True
    5. except ValueError:
    6. pass
    7. try:
    8. import unicodedata
    9. unicodedata.numeric(num)
    10. return True
    11. except (TypeError, ValueError):
    12. pass
    13. return False

    5. 运算:

    1. def calculate(self):
    2. stack = Stack()
    3. while expLength := len(self.suffixExpList):
    4. poppedItem = self.suffixExpList.pop(0)
    5. if poppedItem in "+-×÷^":
    6. num2 = stack.pop()
    7. num1 = stack.pop()
    8. if poppedItem == "+":
    9. newNum = self.add(num1, num2)
    10. elif poppedItem == "-":
    11. newNum = self.sub(num1, num2)
    12. elif poppedItem == "×":
    13. newNum = self.mul(num1, num2)
    14. elif poppedItem == "÷":
    15. newNum = self.dev(num1, num2)
    16. else:
    17. newNum = self.power(num1, num2)
    18. stack.push(newNum)
    19. elif poppedItem == "!":
    20. num = stack.pop()
    21. newNum = self.factorial(num)
    22. stack.push(newNum)
    23. else:
    24. stack.push(float(poppedItem))
    25. expLength -= 1
    26. if len(stack) == 1:
    27. return stack.pop()

    这个函数中引用了几个自定义的运算函数:

    1. // 加
    2. def add(self, num1, num2):
    3. return num1 + num2
    4. // 减
    5. def sub(self, num1, num2):
    6. return num1 - num2
    7. // 乘
    8. def mul(self, num1, num2):
    9. return num1 * num2
    10. // 除
    11. def dev(self, num1, num2):
    12. return num1 / num2
    13. // 指数
    14. def power(self, num1, num2):
    15. return math.pow(num1, num2)
    16. // 阶乘
    17. def factorial(self, num):
    18. result = 1
    19. for i in range(2, int(num)+1):
    20. result *= i
    21. return result

    6. 执行函数:

    1. def execute(self):
    2. if self.preCheck():
    3. self.formatChange()
    4. self.makeSuffixExp()
    5. return self.cancelZero(self.calculate())
    6. else:
    7. return "ERROR"

    这个函数里引用的cancelZero函数的作用是如果结果的小数位为0,则在显示时去掉小数部分:

    1. def cancelZero(self, num):
    2. if str(num).endswith(".0"):
    3. return int(num)
    4. else:
    5. return num

    7. 栈的实现:

    在第 5 步运算这个函数中,使用了 Stack 类的实例,这个类的实现放在这里,就是一个简单的用链表实现的栈:

    1. class Node:
    2. def __init__(self, data, next=None):
    3. self.data = data
    4. self.next = next
    5. class Stack:
    6. def __init__(self):
    7. self.head = None
    8. def __iter__(self):
    9. def visitNodes(node):
    10. if node != None:
    11. visitNodes(node.next)
    12. tempList.append(node.data)
    13. tempList = list()
    14. visitNodes(self.head)
    15. return iter(tempList)
    16. def peak(self):
    17. if self.isEmpty():
    18. raise KeyError("The stack is empty.")
    19. return self.head.data
    20. def isEmpty(self):
    21. return self.head is None
    22. def __len__(self):
    23. if cur := self.head:
    24. self.size = 1
    25. else:
    26. return 0
    27. while cur.next is not None:
    28. self.size += 1
    29. cur = cur.next
    30. return self.size
    31. def push(self, data):
    32. self.head = Node(data, self.head)
    33. def pop(self):
    34. poppedData = self.head.data
    35. self.head = self.head.next
    36. return poppedData

    8. 使用 tkinter 搞个可视化界面:

    tkinter 在这个项目中的作用就比较简单了,我主要的想法就是在窗口最上部放一个显示屏,下边都是计算器的各种按钮,布局比较简单。为了不逼死强迫症(我就是强迫症),我设计了5X5共25个按钮,在以上的基础上又加入了开根号、清除一个数字的C按钮、清除所有数字的CE按钮。以下是我的代码,抛砖引玉吧:

    1. import tkinter as tk
    2. from turtle import bgcolor
    3. from calculator import Expression
    4. window = tk.Tk()
    5. window.title("计算器")
    6. window.geometry('450x490')
    7. window.configure(background="#4169e1", cursor="mouse")
    8. exp = tk.StringVar()
    9. exp.set('')
    10. entry_panel = tk.Entry(window, textvariable=exp, width=25, font=("Arial", 20), foreground="#00bfff", background="#191970", relief="sunken")
    11. entry_panel.grid(row=0, column=4, columnspan=5)
    12. //输入字符的功能
    13. def enterNum(num):
    14. if exp.get() == "ERROR":
    15. exp.set("")
    16. exp.set(exp.get() + num)
    17. //清空显示屏的功能
    18. def clearEntry():
    19. exp.set('')
    20. //退格功能
    21. def backspace():
    22. exp.set(exp.get()[:-1])
    23. //开二次根号功能
    24. def sqrtExec():
    25. a = Expression(exp.get())
    26. exp.set(a.sqrtExec())
    27. //计算
    28. def calculate():
    29. a = Expression(exp.get())
    30. exp.set(str(a.execute()))
    31. w = 10
    32. h = 5
    33. buttonOne = tk.Button(text='1', width=w, height=h, command=lambda: enterNum('1'), font=(30), background="#00bfff")
    34. buttonOne.grid(row=2, column=4)
    35. buttonTwo = tk.Button(text='2', width=w, height=h, command=lambda: enterNum('2'), font=(30), background="#00bfff")
    36. buttonTwo.grid(row=2, column=5)
    37. buttonThree = tk.Button(text='3', width=w, height=h, command=lambda: enterNum('3'), font=(30), background="#00bfff")
    38. buttonThree.grid(row=2, column=6)
    39. buttonFour = tk.Button(text='4', width=w, height=h, command=lambda: enterNum('4'), font=(30), background="#00bfff")
    40. buttonFour.grid(row=3, column=4)
    41. buttonFive = tk.Button(text='5', width=w, height=h, command=lambda: enterNum('5'), font=(30), background="#00bfff")
    42. buttonFive.grid(row=3, column=5)
    43. buttonSix = tk.Button(text='6', width=w, height=h, command=lambda: enterNum('6'), font=(30), background="#00bfff")
    44. buttonSix.grid(row=3, column=6)
    45. buttonSeven = tk.Button(text='7', width=w, height=h, command=lambda: enterNum('7'), font=(30), background="#00bfff")
    46. buttonSeven.grid(row=4, column=4)
    47. buttonEight = tk.Button(text='8', width=w, height=h, command=lambda: enterNum('8'), font=(30), background="#00bfff")
    48. buttonEight.grid(row=4, column=5)
    49. buttonNine = tk.Button(text='9', width=w, height=h, command=lambda: enterNum('9'), font=(30), background="#00bfff")
    50. buttonNine.grid(row=4, column=6)
    51. buttonClear = tk.Button(text='CE', width=w, height=h, command=clearEntry, font=(30), background="#1e90ff")
    52. buttonClear.grid(row=2, column=8)
    53. buttonZero = tk.Button(text='0', width=w, height=h, command=lambda: enterNum('0'), font=(30), background="#00bfff")
    54. buttonZero.grid(row=5, column=5)
    55. buttonBackspace = tk.Button(text='C', width=w, height=h, command=backspace, font=(30), background="#1e90ff")
    56. buttonBackspace.grid(row=2, column=7)
    57. buttonAdd = tk.Button(text='+', width=w, height=h, command=lambda: enterNum('+'), font=(30), background="#87cefa")
    58. buttonAdd.grid(row=3, column=7)
    59. buttonMinus = tk.Button(text='-', width=w, height=h, command=lambda: enterNum('-'), font=(30), background="#87cefa")
    60. buttonMinus.grid(row=4, column=7)
    61. buttonMulti = tk.Button(text='×', width=w, height=h, command=lambda: enterNum('×'), font=(30), background="#87cefa")
    62. buttonMulti.grid(row=5, column=7)
    63. buttonDevide = tk.Button(text='÷', width=w, height=h, command=lambda: enterNum('÷'), font=(30), background="#87cefa")
    64. buttonDevide.grid(row=6, column=7)
    65. buttonCalc = tk.Button(text='=', width=w, height=h, command=calculate, font=(30), background="#468284")
    66. buttonCalc.grid(row=6, column=8)
    67. buttonLeftPar = tk.Button(text='(', width=w, height=h, command=lambda: enterNum('('), font=(30), background="#87cefa")
    68. buttonLeftPar.grid(row=6, column=5)
    69. buttonRightPar = tk.Button(text=')', width=w, height=h, command=lambda: enterNum(')'), font=(30), background="#87cefa")
    70. buttonRightPar.grid(row=6, column=6)
    71. buttonDot = tk.Button(text='.', width=w, height=h, command=lambda: enterNum('.'), font=(30), background="#00bfff")
    72. buttonDot.grid(row=6, column=4)
    73. buttonPower = tk.Button(text='^', width=w, height=h, command=lambda: enterNum('^'), font=(30), background="#87cefa")
    74. buttonPower.grid(row=3, column=8)
    75. buttonSqrt = tk.Button(text='√', width=w, height=h, command=sqrtExec, font=(30), background="#87cefa")
    76. buttonSqrt.grid(row=4, column=8)
    77. buttonFact = tk.Button(text='!', width=w, height=h, command=lambda: enterNum('!'), font=(30), background="#87cefa")
    78. buttonFact.grid(row=5, column=8)
    79. buttonDouZero = tk.Button(text='00', width=w, height=h, command=lambda: enterNum('00'), font=(30), background="#00bfff")
    80. buttonDouZero.grid(row=5, column=4)
    81. buttonPi = tk.Button(text='π', width=w, height=h, command=lambda: enterNum('π'), font=(30), background="#00bfff")
    82. buttonPi.grid(row=5, column=6)
    83. window.mainloop()

    三. 操作演示:

  • 相关阅读:
    操作系统实验四 进程间通信
    AutoHotkey点击命令,鼠标移动到固定坐标单击左键或者双击
    〖Python 数据库开发实战 - MySQL篇⑨〗- 什么是 SQL 语言、如何创建数据逻辑库及如何创建数据表
    电脑重装系统后usbcleaner怎么格式化u盘
    RabbitMQ 入门系列:9、扩展内容:死信队列:真不适合当延时队列。
    Spring中的<lookup-method>标签的功能简介说明
    Linux Kernel入门到精通系列讲解(RV-Kernel 篇) 5.2 从零移植 Ubuntu,基于RISC-V
    机器学习之决策树
    web安全学习笔记(12)
    ICLR 2022最佳论文解读
  • 原文地址:https://blog.csdn.net/yspg_217/article/details/126388927