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. 创建一个计算器类:

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

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

2. 格式检查:

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

def preCheck(self):
    exp = self.exp
    for index in range(len(exp)):
        if exp[index] in "+-×÷" and exp[index - 1] in "+-×÷":
            return False
        if exp[index] == "." and exp[index - 1] == ".":
            return False
    return self.exp

3. 格式改造:

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

def formatChange(self):
    self.midfix = []
    tmp = ''
    for index in range(len(self.exp)):
        // 如果第一个字符是负号“-”
        if index == 0 and self.exp[index] == "-":
            tmp += "-"
        // 如果字符是负号“-”且前一个字符是左括号
        elif index > 0 and self.exp[index - 1] == "(" and self.exp[index] == "-":
            tmp += str(self.exp[index])
        // 如果字符是数字或小数点或“π”
        elif self.exp[index].isdigit() or self.exp[index] == '.' or self.exp[index] == 'π':
            tmp += str(self.exp[index])
        else:
            if tmp != '':
                self.midfix.append(tmp)
                tmp = ''
            self.midfix.append(self.exp[index])
    if tmp != '':
        self.midfix.append(tmp)
    return self.midfix

4. 转换后缀表达式:

def makeSuffixExp(self):
    opList = []
    opDict = self.opDict
    self.suffixExpList = []
    for item in self.midfix:
        // 如果是数
        if self.isNumber(item):
            self.suffixExpList.append(item)
        // 如果是π
        if item == 'π':
            self.suffixExpList.append(str(math.pi))
        // 如果是这几种运算符号(+-×÷^!)
        if item in "+-×÷^!":
            if opList == []:
                opList.append(item)
            else:
                if opDict[item] <= opDict[opList[len(opList)-1]]:
                    for index in range(len(opList)):
                        self.suffixExpList.append(opList[index])
                        del opList[index]
                opList.append(item)
        // 如果是左括号
        if item == "(":
            opList.append(item)
        // 如果是右括号
        if item == ")":
            pt = len(opList) - 1
            while opList[pt] != "(":
                pt -= 1
            pt += 1
            for index in range(pt, len(opList)):
                self.suffixExpList.append(opList[index])
                opList.pop(index)
            opList.pop()
    self.suffixExpList.extend(opList[::-1])
    return self.suffixExpList

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

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

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

def isNumber(self, num):
    try:
        float(num)
        return True
    except ValueError:
        pass
    try:
        import unicodedata
        unicodedata.numeric(num)
        return True
    except (TypeError, ValueError):
        pass
    return False

5. 运算:

def calculate(self):
    stack = Stack()
    while expLength := len(self.suffixExpList):
        poppedItem = self.suffixExpList.pop(0)
        if poppedItem in "+-×÷^":
            num2 = stack.pop()
            num1 = stack.pop()
            if poppedItem == "+":
                newNum = self.add(num1, num2)
            elif poppedItem == "-":
                newNum = self.sub(num1, num2)
            elif poppedItem == "×":
                newNum = self.mul(num1, num2)
            elif poppedItem == "÷":
                newNum = self.dev(num1, num2)
            else:
                newNum = self.power(num1, num2)
            stack.push(newNum)
        elif poppedItem == "!":
            num = stack.pop()
            newNum = self.factorial(num)
            stack.push(newNum)
        else:
            stack.push(float(poppedItem))
        expLength -= 1
    if len(stack) == 1:
        return stack.pop()

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

// 加
def add(self, num1, num2):
    return num1 + num2
// 减
def sub(self, num1, num2):
    return num1 - num2
// 乘
def mul(self, num1, num2):
    return num1 * num2
// 除
def dev(self, num1, num2):
    return num1 / num2
// 指数
def power(self, num1, num2):
    return math.pow(num1, num2)
// 阶乘
def factorial(self, num):
    result = 1
    for i in range(2, int(num)+1):
        result *= i
    return result

6. 执行函数:

def execute(self):
    if self.preCheck():
        self.formatChange()
        self.makeSuffixExp()
        return self.cancelZero(self.calculate())
    else:
        return "ERROR"

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

def cancelZero(self, num):
    if str(num).endswith(".0"):
        return int(num)
    else:
        return num

7. 栈的实现:

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

class Node:

    def __init__(self, data, next=None):
        self.data = data
        self.next = next

class Stack:

    def __init__(self):
        self.head = None

    def __iter__(self):
        def visitNodes(node):
            if node != None:
                visitNodes(node.next)
                tempList.append(node.data)
        tempList = list()
        visitNodes(self.head)
        return iter(tempList)

    def peak(self):
        if self.isEmpty():
            raise KeyError("The stack is empty.")
        return self.head.data
    
    def isEmpty(self):
        return self.head is None

    def __len__(self):
        if cur := self.head:
            self.size = 1
        else:
            return 0
        while cur.next is not None:
            self.size += 1
            cur = cur.next
        return self.size

    def push(self, data):
        self.head = Node(data, self.head)

    def pop(self):
        poppedData = self.head.data
        self.head = self.head.next
        return poppedData

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

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

import tkinter as tk
from turtle import bgcolor
from calculator import Expression

window = tk.Tk()
window.title("计算器")
window.geometry('450x490')
window.configure(background="#4169e1", cursor="mouse")
exp = tk.StringVar()
exp.set('')
entry_panel = tk.Entry(window, textvariable=exp, width=25, font=("Arial", 20), foreground="#00bfff", background="#191970", relief="sunken")
entry_panel.grid(row=0, column=4, columnspan=5)

//输入字符的功能
def enterNum(num):
    if exp.get() == "ERROR":
        exp.set("")
    exp.set(exp.get() + num)

//清空显示屏的功能
def clearEntry():
    exp.set('')

//退格功能
def backspace():
    exp.set(exp.get()[:-1])

//开二次根号功能
def sqrtExec():
    a = Expression(exp.get())
    exp.set(a.sqrtExec())

//计算
def calculate():
    a = Expression(exp.get())
    exp.set(str(a.execute()))

w = 10
h = 5

buttonOne = tk.Button(text='1', width=w, height=h, command=lambda: enterNum('1'), font=(30), background="#00bfff")
buttonOne.grid(row=2, column=4)
buttonTwo = tk.Button(text='2', width=w, height=h, command=lambda: enterNum('2'), font=(30), background="#00bfff")
buttonTwo.grid(row=2, column=5)
buttonThree = tk.Button(text='3', width=w, height=h, command=lambda: enterNum('3'), font=(30), background="#00bfff")
buttonThree.grid(row=2, column=6)
buttonFour = tk.Button(text='4', width=w, height=h, command=lambda: enterNum('4'), font=(30), background="#00bfff")
buttonFour.grid(row=3, column=4)
buttonFive = tk.Button(text='5', width=w, height=h, command=lambda: enterNum('5'), font=(30), background="#00bfff")
buttonFive.grid(row=3, column=5)
buttonSix = tk.Button(text='6', width=w, height=h, command=lambda: enterNum('6'), font=(30), background="#00bfff")
buttonSix.grid(row=3, column=6)
buttonSeven = tk.Button(text='7', width=w, height=h, command=lambda: enterNum('7'), font=(30), background="#00bfff")
buttonSeven.grid(row=4, column=4)
buttonEight = tk.Button(text='8', width=w, height=h, command=lambda: enterNum('8'), font=(30), background="#00bfff")
buttonEight.grid(row=4, column=5)
buttonNine = tk.Button(text='9', width=w, height=h, command=lambda: enterNum('9'), font=(30), background="#00bfff")
buttonNine.grid(row=4, column=6)
buttonClear = tk.Button(text='CE', width=w, height=h, command=clearEntry, font=(30), background="#1e90ff")
buttonClear.grid(row=2, column=8)
buttonZero = tk.Button(text='0', width=w, height=h, command=lambda: enterNum('0'), font=(30), background="#00bfff")
buttonZero.grid(row=5, column=5)
buttonBackspace = tk.Button(text='C', width=w, height=h, command=backspace, font=(30), background="#1e90ff")
buttonBackspace.grid(row=2, column=7)
buttonAdd = tk.Button(text='+', width=w, height=h, command=lambda: enterNum('+'), font=(30), background="#87cefa")
buttonAdd.grid(row=3, column=7)
buttonMinus = tk.Button(text='-', width=w, height=h, command=lambda: enterNum('-'), font=(30), background="#87cefa")
buttonMinus.grid(row=4, column=7)
buttonMulti = tk.Button(text='×', width=w, height=h, command=lambda: enterNum('×'), font=(30), background="#87cefa")
buttonMulti.grid(row=5, column=7)
buttonDevide = tk.Button(text='÷', width=w, height=h, command=lambda: enterNum('÷'), font=(30), background="#87cefa")
buttonDevide.grid(row=6, column=7)
buttonCalc = tk.Button(text='=', width=w, height=h, command=calculate, font=(30), background="#468284")
buttonCalc.grid(row=6, column=8)
buttonLeftPar = tk.Button(text='(', width=w, height=h, command=lambda: enterNum('('), font=(30), background="#87cefa")
buttonLeftPar.grid(row=6, column=5)
buttonRightPar = tk.Button(text=')', width=w, height=h, command=lambda: enterNum(')'), font=(30), background="#87cefa")
buttonRightPar.grid(row=6, column=6)
buttonDot = tk.Button(text='.', width=w, height=h, command=lambda: enterNum('.'), font=(30), background="#00bfff")
buttonDot.grid(row=6, column=4)
buttonPower = tk.Button(text='^', width=w, height=h, command=lambda: enterNum('^'), font=(30), background="#87cefa")
buttonPower.grid(row=3, column=8)
buttonSqrt = tk.Button(text='√', width=w, height=h, command=sqrtExec, font=(30), background="#87cefa")
buttonSqrt.grid(row=4, column=8)
buttonFact = tk.Button(text='!', width=w, height=h, command=lambda: enterNum('!'), font=(30), background="#87cefa")
buttonFact.grid(row=5, column=8)
buttonDouZero = tk.Button(text='00', width=w, height=h, command=lambda: enterNum('00'), font=(30), background="#00bfff")
buttonDouZero.grid(row=5, column=4)
buttonPi = tk.Button(text='π', width=w, height=h, command=lambda: enterNum('π'), font=(30), background="#00bfff")
buttonPi.grid(row=5, column=6)

window.mainloop()

三. 操作演示:

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的基于Tkinter计算器实现示例: ```python import tkinter as tk class Calculator: def __init__(self): self.root = tk.Tk() self.root.title("Calculator") # 创建显示面板 self.display = tk.Entry(self.root, width=20, font=('Arial', 16)) self.display.grid(row=0, column=0, columnspan=4, pady=10) # 添加按钮 # 第一行 btn_clear = tk.Button(self.root, text='C', width=5, height=2, command=self.clear) btn_clear.grid(row=1, column=0) btn_divide = tk.Button(self.root, text='/', width=5, height=2, command=lambda: self.button_click('/')) btn_divide.grid(row=1, column=1) btn_multiply = tk.Button(self.root, text='*', width=5, height=2, command=lambda: self.button_click('*')) btn_multiply.grid(row=1, column=2) btn_delete = tk.Button(self.root, text='DEL', width=5, height=2, command=self.delete) btn_delete.grid(row=1, column=3) # 第二行 btn_7 = tk.Button(self.root, text='7', width=5, height=2, command=lambda: self.button_click('7')) btn_7.grid(row=2, column=0) btn_8 = tk.Button(self.root, text='8', width=5, height=2, command=lambda: self.button_click('8')) btn_8.grid(row=2, column=1) btn_9 = tk.Button(self.root, text='9', width=5, height=2, command=lambda: self.button_click('9')) btn_9.grid(row=2, column=2) btn_minus = tk.Button(self.root, text='-', width=5, height=2, command=lambda: self.button_click('-')) btn_minus.grid(row=2, column=3) # 第三行 btn_4 = tk.Button(self.root, text='4', width=5, height=2, command=lambda: self.button_click('4')) btn_4.grid(row=3, column=0) btn_5 = tk.Button(self.root, text='5', width=5, height=2, command=lambda: self.button_click('5')) btn_5.grid(row=3, column=1) btn_6 = tk.Button(self.root, text='6', width=5, height=2, command=lambda: self.button_click('6')) btn_6.grid(row=3, column=2) btn_plus = tk.Button(self.root, text='+', width=5, height=2, command=lambda: self.button_click('+')) btn_plus.grid(row=3, column=3) # 第四行 btn_1 = tk.Button(self.root, text='1', width=5, height=2, command=lambda: self.button_click('1')) btn_1.grid(row=4, column=0) btn_2 = tk.Button(self.root, text='2', width=5, height=2, command=lambda: self.button_click('2')) btn_2.grid(row=4, column=1) btn_3 = tk.Button(self.root, text='3', width=5, height=2, command=lambda: self.button_click('3')) btn_3.grid(row=4, column=2) btn_equal = tk.Button(self.root, text='=', width=5, height=2, command=self.calculate) btn_equal.grid(row=4, column=3) # 第五行 btn_0 = tk.Button(self.root, text='0', width=5, height=2, command=lambda: self.button_click('0')) btn_0.grid(row=5, column=0) btn_dot = tk.Button(self.root, text='.', width=5, height=2, command=lambda: self.button_click('.')) btn_dot.grid(row=5, column=1) btn_left = tk.Button(self.root, text='(', width=5, height=2, command=lambda: self.button_click('(')) btn_left.grid(row=5, column=2) btn_right = tk.Button(self.root, text=')', width=5, height=2, command=lambda: self.button_click(')')) btn_right.grid(row=5, column=3) def run(self): self.root.mainloop() def button_click(self, char): self.display.insert(tk.END, char) def clear(self): self.display.delete(0, tk.END) def delete(self): self.display.delete(len(self.display.get()) - 1) def calculate(self): try: result = eval(self.display.get()) self.display.delete(0, tk.END) self.display.insert(tk.END, result) except: self.display.delete(0, tk.END) self.display.insert(tk.END, "Error") if __name__ == '__main__': calculator = Calculator() calculator.run() ``` 这个实现非常基础,只支持基本的加减乘除和括号运算,并且没有做输入安全性检查,仅供参考。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值