简易计算器的实现python

学习目标:

掌握数据结构和算法设计的方法,

学习GUI图形界面的设计,Python Tinker


学习内容:

  1. 算术表达式求解,是指算术表达式中包括加、减、乘、除、括号等运算符,能求解包含括号的四则混合运算;
  2. 能够检验表达式的合法性。
  3.  运用GUI图形界面的设计,Python Tinker     得到计算器界面,能通过界面按钮控件输入并实现算术表达式

学习成果:

 计算器界面设计:

一个计算器的类,可以在窗口中创建一个简单的计算器界面。它使用了Tkinter库来实现窗口和按钮的布局,以及计算器功能的实现。

该类的初始化方法__init__接受一个窗口对象作为参数,并设置窗口的标题和背景颜色。然后创建一个滚动条组件,并将其与输入框关联。创建一个输入框,并设置其属性,包括高亮颜色、宽度等。接着,创建一个按钮的列表,每个按钮都有特定的文本和功能。根据按钮的位置将它们添加到窗口中,并为每个按钮设置相应的回调函数。最后,设置输入框获取焦点。

类中还定义了一些辅助方法,包括清空输入框、求解表达式、添加字符、删除字符等。

你可以使用这个类创建一个计算器窗口,并实现基本的计算功能。

计算器算法实现:

实现中缀表达式转后缀表达式,并计算后缀表达式结果的方法。主要分为三个函数:

dealstr(expression) 函数用于处理字符串中的数学函数(如math.sin、math.cos等),将其替换为对应的结果值。例如,将"2*math.sin(0.5)"替换为"1.682941969615793"。

midToAfter(expression) 函数将中缀表达式转为后缀表达式的形式。遍历表达式中的每个字符,如果是数字或者小数点,则直接添加到后缀表达式列表中;如果是运算符(+、-、*、/),则根据优先级判断是否需要入栈或出栈。最后将剩余在栈中的运算符依次出栈并添加到后缀表达式列表中。

Suffix_value(expression) 函数通过处理后缀表达式列表来计算表达式的值。遍历后缀表达式中的每个元素,如果是数字,则将其转换成浮点型并入栈;如果是运算符,则从栈中弹出两个操作数,并进行相应的运算,再将结果入栈。最后栈中只剩下一个元素,即为计算得到的结果。

整个方法的作用是输入一个中缀表达式字符串,返回计算得到的结果。

注意,这段代码依赖于math模块中的数学函数,因此在使用前需要先导入math模块。另外,注意处理除数为0的情况,可能需要进行额外处理。

from tkinter import *
from tkinter import Tk
from tkinter import StringVar, Entry, Button, scrolledtext, Radiobutton
import math


# 定义计算器类
class Calculator:
    def __init__(self, window):
        window.title('计算器')
        window.configure(background="white")
        # 创建滚动条组件
        bar = scrolledtext.Scrollbar(window, orient=HORIZONTAL)  # orient设置水平
        bar.grid(row=1, column=3, sticky=(W, E))  # 设置东西方向占满
        self.string = StringVar()  # 跟踪文本框内容的变量
        entry = Entry(window, textvariable=self.string, xscrollcommand=bar.set, highlightthickness=80,
                      highlightcolor='pink', width=65)
        bar.config(command=entry.xview)  # command和输入框组件关联
        entry.grid(row=0, column=0, columnspan=100)
        # entry.configure(background="white")#设置输入框的背景颜色
        entry.focus()  # 获取焦点

        values = ["sqrt", ",", "(", ")", "pow", "进制转化",   #开根
                  "gcd", "C", "DEL", "%", "/", "sin",       #最大公约数
                  "radians", "7", "8", "9", "*", "cos",     #弧度
                  "degrees", "4", "5", "6", "-", "tan",     # 度
                  "exp", "1", "2", "3", "+", "pi",          #指数      Π
                  "log", "e", "0", ".", "="]                #对数

        i = 0
        row = 2
        col = 0
        for txt in values:
            padx = 20  # 每个按键的长和宽
            pady = 20
            if i % 6 == 0:  # 控制每个按钮的布局
                row = row + 1
                col = 0
            # 单独设置每个建的功能
            if (txt == '='):
                btn = Button(window, height=2, width=17, padx=padx, pady=pady, text=txt,
                             command=lambda txt=txt: self.SOLVE())
                btn.grid(row=row, column=col, columnspan=2)
                btn.configure(background="orange")

            elif (txt == 'DEL'):
                btn = Button(window, height=2, width=4, padx=padx, pady=pady, text=txt,
                             command=lambda: self.delete())
                btn.grid(row=row, column=col, padx=1, pady=1)
                btn.configure(background="orange")
            elif (txt == 'C'):
                btn = Button(window, height=2, width=4, padx=padx, pady=pady, text=txt,
                             command=lambda: self.clearall())
                btn.grid(row=row, column=col, padx=1, pady=1)
                btn.configure(background="orange")
            elif (txt == '进制转化'):  # 点击此按钮会新建一个窗口
                btn = Button(window, height=2, width=4, padx=padx, pady=pady, text=txt,
                             command=self.count_change)
                btn.grid(row=row, column=col, padx=1, pady=1)
                btn.configure(background="red")
            else:
                btn = Button(window, height=2, width=4, padx=padx, pady=pady, text=txt,
                             command=lambda txt=txt: self.addChar(txt))
                btn.grid(row=row, column=col, padx=1, pady=1)
                btn.configure(background="white")

            col = col + 1
            i = i + 1

    # 清空输入框的方法
    def clearall(self):
        self.string.set("")

    # 等号绑定的方法
    def SOLVE(self):
        result = ""
        try:
            # 普通带括号的四则运算和sin cos exp 用中缀转后缀求值
            result = self.Infix_To_Suffix_value(self.string.get())  # 获得最终结果
            self.string.set(result)
        except:
            # 如果是其他的没写尝试使用内置函数
            try:
                print("使用了eval")
                result = eval(self.string.get())
                self.string.set(result)
            except:
                # 如果内置函数都求不出来就报错
                result = '输入格式错误'
                self.string.set(result)

    # 添加math的一些功能
    def addChar(self, char):
        i = ['log', 'sqrt', 'pi', 'sin', 'cos', 'tan', 'e', "gcd", "radians", "degrees", "exp"]
        if char in i:
            self.string.set(self.string.get() + 'math.' + (str(char)))
        else:
            self.string.set(self.string.get() + (str(char)))

    # 删除键
    def delete(self):
        self.string.set(self.string.get()[0:-1])

    # 输入中缀表达式会先转成后缀表达式最后求值返回一个结果
    def Infix_To_Suffix_value(self, expression):
        # 处理最开始的字符串
        def dealstr(expression):
            s = expression
            pattern_sin = r'\bmath.sin\b.*?[)]'
            pattern_cos = r'\bmath.cos\b.*?[)]'
            pattern_exp = r'\bmath.exp\b.*?[)]'
            sin_list = re.findall(pattern_sin, s)
            cos_list = re.findall(pattern_cos, s)
            exp_list = re.findall(pattern_exp, s)
            if sin_list:
                for old in sin_list:
                    new = str(math.sin(m_a_v(old[9:-1])))
                    s = s.replace(old, new)
            # print(s)
            if cos_list:
                for old in cos_list:
                    new = str(math.cos(m_a_v(old[9:-1])))
                    s = s.replace(old, new)
            # print(s)
            if exp_list:
                for old in exp_list:
                    new = str(math.exp(m_a_v(old[9:-1])))
                    s = s.replace(old, new)
            # print(s)
            return s

        # 设置优先级
        def priority(z):
            if z in ['*', '/']:
                return 2
            elif z in ['+', '-']:
                return 1

        # 中缀转后缀
        def midToAfter(expression):
            stack = []  # 存储栈用来存储运算符
            post = []  # 后缀表达式存储
            flag = 0  # 判断是否为小数和多位数的标志flag
            for z in expression:
                if z not in ['*', '/', '+', '-', '(', ')']:  # 字符直接添加到后缀表达式存储列表中
                    if flag == 0:
                        post.append(z)
                        flag = 1
                    else:
                        # 当为数字或者'.'的时候就把当前z拼接到最后一个值后面
                        post[-1] = post[-1] + z
                else:
                    flag = 0  # flag置0表明遇到了符号则上一个数存储完毕
                    if z != ')' and (not stack or z == '(' or stack[-1] == '('
                                     or priority(z) > priority(stack[-1])):  # stack 不空;栈顶为(;优先级大于
                        stack.append(z)  # 运算符入栈

                    elif z == ')':  # 右括号出栈
                        while True:
                            x = stack.pop()
                            if x != '(':
                                post.append(x)
                            else:
                                break

                    else:  # 比较运算符优先级,看是否入栈出栈
                        while True:
                            if stack and stack[-1] != '(' and priority(z) <= priority(stack[-1]):
                                post.append(stack.pop())
                            else:
                                stack.append(z)
                                break

            while stack:  # 还未出栈的运算符,需要加到表达式末尾
                post.append(stack.pop())
            return post

        # 处理后缀表达式得到结果
        def Suffix_value(expression):
            S1 = []
            for i in expression:
                if i not in ['+', '-', '*', '/']:
                    i = float(i)  # 将字符数字装成浮点类型
                    S1.append(i)
                else:
                    # s2是第一个s1是第二个注意顺序
                    if i == '+':
                        s2 = S1.pop()
                        if S1:  # 判断非空才弹出后一个
                            s1 = S1.pop()
                        else:
                            s1 = 0
                        S1.append(s1 + s2)
                    if i == '-':
                        s2 = S1.pop()
                        if S1:
                            s1 = S1.pop()
                        else:
                            s1 = 0
                        S1.append(s1 - s2)
                    if i == '*':
                        s2 = S1.pop()
                        if S1:
                            s1 = S1.pop()
                        else:
                            s1 = 0
                        S1.append(s1 * s2)
                    if i == '/':
                        s2 = S1.pop()
                        if S1:
                            s1 = S1.pop()
                        else:
                            s1 = 0
                        S1.append(s1 / s2)
            return S1[0]

        # post=midToAfter(expression)
        # result = str(Suffix_value(post))  # 得到最终的结果
        # 将尾数为'.0'转换成整数显示
        # if result[-1] == '0':
        #  result = result[0:-2]
        # ------------------------------------------
        # 进去中缀出来值
        def m_a_v(expression):
            value = Suffix_value(midToAfter(expression))
            return value

        result = str(m_a_v(dealstr(expression)))
        if result[-1] == '0':
            result = result[0:-2]
        return result

    # 进制转化函数
    @staticmethod
    def count_change():
        # 定义进制转换类
        class Programmer():
            count = 0  # 类变量来记录选中的进制

            def __init__(self):
                window_top = Toplevel(root)  # 弹出一个在root之上的窗口
                window_top.transient(root)  # 窗口只置顶root之上
                window_top.resizable(False, False)  # 不可调节窗体大小
                window_top.title('进制转换界面')
                # 跟踪文本框内容的变量
                self.string = StringVar()
                self.string_HEX = StringVar()
                self.string_DEC = StringVar()
                self.string_OCT = StringVar()
                self.string_BIN = StringVar()
                self.ALL = [[16, self.string_HEX], [10, self.string_DEC], [8, self.string_OCT], [2, self.string_BIN]]
                self.var = StringVar()
                Radiobutton(window_top, text="HEX", variable=self.var, value="HEX", indicatoron=False,
                            command=self.chose_key,
                            width=10).grid(row=0, column=0)
                Radiobutton(window_top, text="DEC", variable=self.var, value="DEC", indicatoron=False,
                            command=self.chose_key,
                            width=10).grid(row=1, column=0)
                Radiobutton(window_top, text="OCT", variable=self.var, value="OCT", indicatoron=False,
                            command=self.chose_key,
                            width=10).grid(row=2, column=0)
                Radiobutton(window_top, text="BIN", variable=self.var, value="BIN", indicatoron=False,
                            command=self.chose_key,
                            width=10).grid(row=3, column=0)
                entry_HEX = Entry(window_top, textvariable=self.string_HEX, width=44).grid(row=0, column=1,
                                                                                           columnspan=4)
                entry_DEC = Entry(window_top, textvariable=self.string_DEC, width=44).grid(row=1, column=1,
                                                                                           columnspan=4)
                entry_OCT = Entry(window_top, textvariable=self.string_OCT, width=44).grid(row=2, column=1,
                                                                                           columnspan=4)
                entry_BIN = Entry(window_top, textvariable=self.string_BIN, width=44).grid(row=3, column=1,
                                                                                           columnspan=4)
                values = ["A", ",", "(", ")", ".",
                          "B", "AC", "DEL", "%", "/",
                          "C", "7", "8", "9", "*",
                          "D", "4", "5", "6", "-",
                          "E", "1", "2", "3", "+",
                          "F", "0", "="]
                i = 0
                row = 4
                col = 0
                A = 10
                for txt in values:
                    if i % 5 == 0:  # 控制一列的按钮个数
                        row = row + 1
                        col = 0
                    # 单独设置每个建的功能
                    if (txt == '='):
                        btn = Button(window_top, height=2, width=33, text=txt, command=self.Equal)
                        btn.grid(row=row, column=col, columnspan=3)
                        btn.configure(background="orange")

                    elif (txt == 'DEL'):
                        btn = Button(window_top, height=2, width=A, text=txt, command=lambda: self.delete())
                        btn.grid(row=row, column=col)
                        btn.configure(background="orange")
                    elif (txt == 'AC'):
                        btn = Button(window_top, height=2, width=A, text=txt, command=lambda: self.clearall())
                        btn.grid(row=row, column=col)
                        btn.configure(background="orange")
                    else:
                        btn = Button(window_top, height=2, width=A, text=txt, command=lambda txt=txt: self.addChar(txt))
                        btn.grid(row=row, column=col)
                        btn.configure(background="white")
                    col = col + 1
                    i = i + 1
                window_top.mainloop()

            def addChar(self, char):
                if self.count == 16:
                    self.string_HEX.set(self.string_HEX.get() + (str(char)))
                if self.count == 10:
                    self.string_DEC.set(self.string_DEC.get() + (str(char)))
                if self.count == 8:
                    self.string_OCT.set(self.string_OCT.get() + (str(char)))
                if self.count == 2:
                    if self.string_BIN.get() == 0:
                        self.string_BIN.set('ob' + self.string_BIN.get())
                    self.string_BIN.set(self.string_BIN.get() + (str(char)))

            # 等于按键的操作,按下按键会使的所有框框显示转换后的值
            def Equal(self):
                try:
                    if self.count == 16:
                        self.string_HEX.set(Programmer.Infix_To_Suffix_value(self.string_HEX.get(), 16, 'x'))
                        self.string_DEC.set(Programmer.Infix_To_Suffix_value(self.string_HEX.get(), 16, ''))
                        self.string_OCT.set(Programmer.Infix_To_Suffix_value(self.string_HEX.get(), 16, 'o'))
                        self.string_BIN.set(Programmer.Infix_To_Suffix_value(self.string_HEX.get(), 16, 'b'))
                    if self.count == 10:
                        self.string_DEC.set(Programmer.Infix_To_Suffix_value(self.string_DEC.get(), 10, ''))
                        self.string_HEX.set(Programmer.Infix_To_Suffix_value(self.string_DEC.get(), 10, 'x'))
                        self.string_OCT.set(Programmer.Infix_To_Suffix_value(self.string_DEC.get(), 10, 'o'))
                        self.string_BIN.set(Programmer.Infix_To_Suffix_value(self.string_DEC.get(), 10, 'b'))
                    if self.count == 8:
                        self.string_OCT.set(Programmer.Infix_To_Suffix_value(self.string_OCT.get(), 8, 'o'))
                        self.string_DEC.set(Programmer.Infix_To_Suffix_value(self.string_OCT.get(), 8, ''))
                        self.string_HEX.set(Programmer.Infix_To_Suffix_value(self.string_OCT.get(), 8, 'x'))
                        self.string_BIN.set(Programmer.Infix_To_Suffix_value(self.string_OCT.get(), 8, 'b'))
                    if self.count == 2:
                        self.string_BIN.set(Programmer.Infix_To_Suffix_value(self.string_BIN.get(), 2, 'b'))
                        self.string_OCT.set(Programmer.Infix_To_Suffix_value(self.string_BIN.get(), 2, 'o'))
                        self.string_DEC.set(Programmer.Infix_To_Suffix_value(self.string_BIN.get(), 2, ''))
                        self.string_HEX.set(Programmer.Infix_To_Suffix_value(self.string_BIN.get(), 2, 'x'))
                except:
                    for i in self.ALL:
                        if i[0] != self.count:
                            i[1].set('输入错误')

            def delete(self):
                for i in self.ALL:
                    if i[0] == self.count:
                        i[1].set(i[1].get()[0:-1])

            def clearall(self):
                for i in self.ALL:
                    if i[0] == self.count:
                        i[1].set('')

            # 判断是哪个按钮按下去了
            def chose_key(self):
                if self.var.get() == 'HEX':
                    self.count = 16
                    print('HEX')
                if self.var.get() == 'DEC':
                    self.count = 10
                    print('DEC')
                if self.var.get() == 'OCT':
                    self.count = 8
                    print('OCT')
                if self.var.get() == 'BIN':
                    self.count = 2
                    print('BIN')

            # 输入中缀表达式会先转成后缀表达式最后求值返回一个结果
            @staticmethod
            def Infix_To_Suffix_value(expression, input: int = 10, out: str = ''):
                '''
                   expression为中缀表达式
                   input为expression的表达式类型eg:2,8,10,16,默认为10,
                   out为输出的类型eg:'b','o','x'默认为''代表10进制
                '''
                if input == 2:
                    a, b, c = ('0b', input, out)
                elif input == 8:
                    a, b, c = ('0o', input, out)
                elif input == 10:
                    a, b, c = ('', input, out)
                else:
                    a, b, c = ('0x', input, out)

                # 设置优先级
                def priority(z):
                    if z in ['*', '/']:
                        return 2
                    elif z in ['+', '-']:
                        return 1

                stack = []  # 存储栈用来存储运算符
                post = []  # 后缀表达式存储
                flag = 0  # 判断是否为小数和多位数的标志flag
                for z in expression:
                    if z not in ['*', '/', '+', '-', '(', ')']:  # 字符直接添加到后缀表达式存储列表中
                        if flag == 0:
                            post.append(z)
                            post[-1] = a + post[-1]  # a
                            flag = 1
                        else:
                            # 当为数字或者'.'的时候就把当前z拼接到最后一个值后面
                            post[-1] = post[-1] + z
                    else:
                        flag = 0  # flag置0表明遇到了符号则上一个数存储完毕
                        if z != ')' and (not stack or z == '(' or stack[-1] == '('
                                         or priority(z) > priority(stack[-1])):  # stack 不空;栈顶为(;优先级大于
                            stack.append(z)  # 运算符入栈

                        elif z == ')':  # 右括号出栈
                            while True:
                                x = stack.pop()
                                if x != '(':
                                    post.append(x)
                                else:
                                    break

                        else:  # 比较运算符优先级,看是否入栈出栈
                            while True:
                                if stack and stack[-1] != '(' and priority(z) <= priority(stack[-1]):
                                    post.append(stack.pop())
                                else:
                                    stack.append(z)
                                    break

                while stack:  # 还未出栈的运算符,需要加到表达式末尾
                    post.append(stack.pop())

                # 处理后缀表达式得到结果
                def Suffix_value(expression):
                    S1 = []
                    for i in expression:
                        if i not in ['+', '-', '*', '/']:
                            i = int(i, b)  # b 将字符数字转成整形进行10进制运算
                            S1.append(i)
                        else:
                            # s2是第一个s1是第二个注意顺序
                            if i == '+':
                                s2 = S1.pop()
                                if S1:
                                    s1 = S1.pop()
                                else:
                                    s1 = 0
                                S1.append(s1 + s2)
                            if i == '-':
                                s2 = S1.pop()
                                if S1:
                                    s1 = S1.pop()
                                else:
                                    s1 = 0
                                S1.append(s1 - s2)
                            if i == '*':
                                s2 = S1.pop()
                                if S1:
                                    s1 = S1.pop()
                                else:
                                    s1 = 0
                                S1.append(s1 * s2)
                            if i == '/':
                                s2 = S1.pop()
                                if S1:
                                    s1 = S1.pop()
                                else:
                                    s1 = 0
                                S1.append(s1 / s2)
                    return S1[0]  # 结果为10进制数

                result = str(format(Suffix_value(post), c))  # c 得到最终的结果
                return result

        # 实例化类
        # print(id(programmer))
        Programmer()


# 启动计算器
root = Tk()
Calculator(root)
root.mainloop()


问题发现:

在写代码的时候,如何解决恶意输入导致报错。

代码是一个计算器程序的 SOLVE() 方法,它首先尝试用中缀表达式转后缀表达式的方法计算输入的表达式,并输出结果;如果该方法无法处理,则使用内置函数 eval() 尝试计算,最终若还无法求解则输出错误提示信息。

其中如果使用了 eval() 函数,需要注意其存在一些安全隐患。由于 eval() 函数会将字符串当做 Python 代码进行执行,因此如果用户输入的表达式中包含了一些恶意的代码,就有可能导致安全问题。

故此时输出“输入格式错误。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值