65.9K
CodeProject 正在变化。 阅读更多。
Home

来编写那个酷炫的计算器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (8投票s)

2023年10月29日

CPOL

8分钟阅读

viewsIcon

40019

downloadIcon

700

通过一个关于如何使用 Python 创建具有许多功能的智能计算器的教程来提高您的编码技能。

引言

我知道你们很多人喜欢我本月早些时候发表的文章,所以如果你还没有机会阅读,或者你愿意运用你的技能学习Python或Web开发,我鼓励你阅读它。

你将学习许多库以及如何将它们结合起来创建一个有用的应用程序。

我喜欢我的文章有用、令人兴奋、富有挑战性,并能模拟真实世界的场景,所以我为你精心制作了这篇教程。我希望你能从中获得有价值的东西。那么,让我们开始吧。

背景

你需要了解Python基础知识才能跟上本教程,因为这不是Python课程。

我建议你阅读这篇文章来熟悉Tkinter,以便做好准备(可选)。

有关sympy库的更多信息,请点击此处

要求

创建一个新的虚拟环境

python -m venv .venv

激活虚拟环境

. .venv/Scripts/activate 

在此处下载文件或复制其内容,然后粘贴到一个名为requirements.txt的新文件中。

使用以下命令安装所需依赖

pip install -r requirements.txt

另外,请下载Tesseract安装程序,它对于图像到文本提取是必需的,并按照显示的步骤操作。在此处下载

实现

要创建一个简单的Tkinter窗口,我们必须导入模块,然后创建一个将包含所有元素的窗口,为其指定一个标题,最后调用窗口。

创建一个名为main.py的新文件,然后编写以下代码

import tkinter as tk

win = tk.Tk()
win.title('Hello world')

win.mainloop()

确保你已激活虚拟环境,然后执行命令来运行程序

python main.py

你应该会看到一个带有Hello world标题的空白窗口。

现在我们需要在窗口中填充一些Tkinter控件。

Tkinter有许多控件,在本教程中,我们将使用ButtonEntryTextFrameLabelFrame(这是一个带有标题的面板)。

每个Tkinter控件都有一个父控件,所以一个按钮可以放在一个窗口或一个框架(面板)内,而框架是一个可以放置控件的容器。

让我们创建一个带有输入框(textbox)和一些按钮的基本界面。

import tkinter as tk

win = tk.Tk()
win.title('Smart Calculator')

frm_txtbox = tk.Frame()
frm_txtbox.pack()

txt_box = tk.Text(master=frm_txtbox, width=32, height=8, font=('default', 16))
txt_box.insert(tk.INSERT, 'Type here your math problem ...')
txt_box.pack()

win.mainloop()

这将创建一个基本的用户界面,其中包含一个用于输入一些信息的输入框。

首先,我们初始化窗口并创建了一个名为frm_txtbox的框架,并通过pack()函数将其放置到位。然后,我们在frm_txtboxmaster=frm_txtbox)中创建了一个Textbox,并设置了一些参数进行自定义。

但是,它什么也做不了,所以让我们更新代码来添加一些按钮。

import tkinter as tk

win = tk.Tk()
win.title('Smart Calculator')

frm_txtbox = tk.Frame()
frm_txtbox.pack()

txt_box = tk.Text(master=frm_txtbox, width=32, height=8, font=('default', 16))
txt_box.insert(tk.INSERT, 'Type here your math problem ...')
txt_box.pack()

frm_standard = tk.LabelFrame(text='Standard', font=('default', 12))
frm_standard.pack()

btn_parentheses_right = tk.Button(master=frm_standard, text='(', width=5, height=2, cursor='hand2', font=('default', 12))
btn_parentheses_left = tk.Button(master=frm_standard, text=')', width=5, height=2, cursor='hand2', font=('default', 12))
btn_seven = tk.Button(master=frm_standard, text='7', bg='white', width=5, height=2, cursor='hand2', font=('default', 12))
btn_eight = tk.Button(master=frm_standard, text='8', bg='white', width=5, height=2, cursor='hand2', font=('default', 12))
btn_nine = tk.Button(master=frm_standard, text='9', bg='white', width=5, height=2, cursor='hand2', font=('default', 12))
btn_divide = tk.Button(master=frm_standard, text='/', width=5, height=2, cursor='hand2', font=('default', 12))

btn_square = tk.Button(master=frm_standard, text='²', width=5, height=2, cursor='hand2', font=('default', 12))
btn_square_root = tk.Button(master=frm_standard, text='√', width=5, height=2, cursor='hand2', font=('default', 12))
btn_four = tk.Button(master=frm_standard, text='4', bg='white', width=5, height=2, cursor='hand2', font=('default', 12))
btn_five = tk.Button(master=frm_standard, text='5', bg='white', width=5, height=2, cursor='hand2', font=('default', 12))
btn_six = tk.Button(master=frm_standard, text='6', bg='white', width=5, height=2, cursor='hand2', font=('default', 12))
btn_multiply = tk.Button(master=frm_standard, text='*', width=5, height=2, cursor='hand2', font=('default', 12))

btn_cube = tk.Button(master=frm_standard, text='³', width=5, height=2, cursor='hand2', font=('default', 12))
btn_cube_root = tk.Button(master=frm_standard, text='∛', width=5, height=2, cursor='hand2', font=('default', 12))
btn_one = tk.Button(master=frm_standard, text='1', bg='white', width=5, height=2, cursor='hand2', font=('default', 12))
btn_two = tk.Button(master=frm_standard, text='2', bg='white', width=5, height=2, cursor='hand2', font=('default', 12))
btn_three = tk.Button(master=frm_standard, text='3', bg='white', width=5, height=2, cursor='hand2', font=('default', 12))
btn_minus = tk.Button(master=frm_standard, text='-', width=5, height=2, cursor='hand2', font=('default', 12))

btn_pi = tk.Button(master=frm_standard, text='Ⲡ', width=5, height=2, cursor='hand2', font=('default', 12))
btn_x = tk.Button(master=frm_standard, text='x', width=5, height=2, cursor='hand2', font=('default', 12))
btn_zero = tk.Button(master=frm_standard, text='0', bg='white', width=5, height=2, cursor='hand2', font=('default', 12))
btn_dot = tk.Button(master=frm_standard, text='.', width=5, height=2, cursor='hand2', font=('default', 12))
btn_equal = tk.Button(master=frm_standard, text='=', width=5, height=2, cursor='hand2', font=('default', 12))
btn_plus = tk.Button(master=frm_standard, text='+', width=5, height=2, cursor='hand2', font=('default', 12))

i,j = 0,0
for btn in frm_standard.children:
    frm_standard.children[btn].grid(row=j, column=i)
    i += 1
    if i == 6:
        i = 0
        j += 1


win.mainloop()

你应该会看到类似这样的结果

Image description

第一个改变是我们添加了标准的计算器按钮,比如数字0到9和基本运算。

然后我创建了两个变量ij,使用grid函数按顺序放置按钮,grid函数需要两个参数,如你所见(行和列)。你可能会问为什么i设置为零而不是六,嗯…一旦我们创建了六个按钮,我们就需要移动到新的一行来插入这些按钮。当然,你可以按照自己的方式添加按钮。但我发现这是正确的顺序。

对于大型应用程序,我们可以避免混淆,并通过将代码拆分成文件来使代码可管理,让我们创建一个名为gui_layout.py的新文件,我们将在此文件中构建GUI的完整布局。

from main import *

#   Layout the standard default panel
def place_std_btns():
    i,j = 0,0
    for btn in frm_standard.children:
        frm_standard.children[btn].grid(row=j, column=i)
        i += 1
        if i == 6:
            i = 0
            j += 1

place_std_btns()
frm_txtbox.pack()
txt_box.pack()
frm_standard.pack()

win.mainloop()

main.py更新如下

import tkinter as tk

win = tk.Tk()
win.title('Smart Calculator')

frm_txtbox = tk.Frame()

txt_box = tk.Text(master=frm_txtbox, width=32, height=8, font=('default', 16))
txt_box.insert(tk.INSERT, 'Type here your math problem ...')

frm_standard = tk.LabelFrame(text='Standard', font=('default', 12))

btn_parentheses_right = tk.Button(master=frm_standard, text='(', width=5, height=2, cursor='hand2', font=('default', 12))
btn_parentheses_left = tk.Button(master=frm_standard, text=')', width=5, height=2, cursor='hand2', font=('default', 12))
btn_seven = tk.Button(master=frm_standard, text='7', bg='white', width=5, height=2, cursor='hand2', font=('default', 12))
btn_eight = tk.Button(master=frm_standard, text='8', bg='white', width=5, height=2, cursor='hand2', font=('default', 12))
btn_nine = tk.Button(master=frm_standard, text='9', bg='white', width=5, height=2, cursor='hand2', font=('default', 12))
btn_divide = tk.Button(master=frm_standard, text='/', width=5, height=2, cursor='hand2', font=('default', 12))

btn_square = tk.Button(master=frm_standard, text='²', width=5, height=2, cursor='hand2', font=('default', 12))
btn_square_root = tk.Button(master=frm_standard, text='√', width=5, height=2, cursor='hand2', font=('default', 12))
btn_four = tk.Button(master=frm_standard, text='4', bg='white', width=5, height=2, cursor='hand2', font=('default', 12))
btn_five = tk.Button(master=frm_standard, text='5', bg='white', width=5, height=2, cursor='hand2', font=('default', 12))
btn_six = tk.Button(master=frm_standard, text='6', bg='white', width=5, height=2, cursor='hand2', font=('default', 12))
btn_multiply = tk.Button(master=frm_standard, text='*', width=5, height=2, cursor='hand2', font=('default', 12))

btn_cube = tk.Button(master=frm_standard, text='³', width=5, height=2, cursor='hand2', font=('default', 12))
btn_cube_root = tk.Button(master=frm_standard, text='∛', width=5, height=2, cursor='hand2', font=('default', 12))
btn_one = tk.Button(master=frm_standard, text='1', bg='white', width=5, height=2, cursor='hand2', font=('default', 12))
btn_two = tk.Button(master=frm_standard, text='2', bg='white', width=5, height=2, cursor='hand2', font=('default', 12))
btn_three = tk.Button(master=frm_standard, text='3', bg='white', width=5, height=2, cursor='hand2', font=('default', 12))
btn_minus = tk.Button(master=frm_standard, text='-', width=5, height=2, cursor='hand2', font=('default', 12))

btn_pi = tk.Button(master=frm_standard, text='Ⲡ', width=5, height=2, cursor='hand2', font=('default', 12))
btn_x = tk.Button(master=frm_standard, text='x', width=5, height=2, cursor='hand2', font=('default', 12))
btn_zero = tk.Button(master=frm_standard, text='0', bg='white', width=5, height=2, cursor='hand2', font=('default', 12))
btn_dot = tk.Button(master=frm_standard, text='.', width=5, height=2, cursor='hand2', font=('default', 12))
btn_equal = tk.Button(master=frm_standard, text='=', width=5, height=2, cursor='hand2', font=('default', 12))
btn_plus = tk.Button(master=frm_standard, text='+', width=5, height=2, cursor='hand2', font=('default', 12))

现在我们有了一个形似计算器的东西,但它仍然… 什么也做不了。

让我们让按钮变得可用,这样当我们点击它们时,它们就会执行一些操作。

更新gui_layout.py

from main import *


#   Layout the standard default panel
def place_std_btns():
    i,j = 0,0
    for btn in frm_standard.children:
        frm_standard.children[btn].grid(row=j, column=i)
        i += 1
        if i == 6:
            i = 0
            j += 1


#   Adds to the text-box what the button contains in it
def insert_btn_txt(btn):
    txt_box.config(state='normal')
    txt_box.insert(tk.END, btn['text'])
    txt_box.config(state='disabled')


#   Make every button functional by assigning a function to it
def assign_btn_funcs():
    for btn in frm_standard.children:
        frm_standard.children[btn]['command'] = lambda x=frm_standard.children[btn]: insert_btn_txt(x)


#   Calls the layout functions above and layout the gui elements
def init_gui_layout():
    place_std_btns()
    frm_txtbox.pack()
    txt_box.pack()
    frm_standard.pack()


assign_btn_funcs()
init_gui_layout()

我们添加了一个名为assign_btn_funcs的函数,使屏幕上的每个按钮都可用,方法是为其分配一个lambda函数,该函数会将按钮包含的内容添加到文本框中。比如,如果我们点击了7,那么7就会被添加到文本框中。

但如果你注意到了,默认文本仍然可见,所以让我们通过添加一个delete函数来删除它,该函数通过调用clear text函数来移除默认文本。

from main import *


#   Layout the standard default panel
def place_std_btns():
    i,j = 0,0
    for btn in frm_standard.children:
        frm_standard.children[btn].grid(row=j, column=i)
        i += 1
        if i == 6:
            i = 0
            j += 1


#   Clears all text from text box
def clear_txt():
    txt_box.config(state='normal')
    txt_box.delete('1.0', tk.END)
    txt_box.config(state='disabled')


#   Deletes 'Type here your math problem ...' to let the user add input
def delete_paceholder():
    if txt_box.get(1.0, "end-1c") == 'Type here your math problem ...':
        clear_txt()


#   Adds to the text-box what the button contains in it
def insert_btn_txt(btn):
    delete_paceholder()
    txt_box.config(state='normal')
    txt_box.insert(tk.END, btn['text'])
    txt_box.config(state='disabled')


#   Make every button functional by assigning a function to it
def assign_btn_funcs():
    for btn in frm_standard.children:
        frm_standard.children[btn]['command'] = lambda x=frm_standard.children[btn]: insert_btn_txt(x)


#   Calls the layout functions above and layout the gui elements
def init_gui_layout():
    place_std_btns()
    frm_txtbox.pack()
    txt_box.pack()
    frm_standard.pack()


assign_btn_funcs()
init_gui_layout()

win.mainloop()

现在我们将重构代码,使一切都放在合适的位置。

我们将有四个文件

Image description

首先,创建一个名为functions.py的新文件,它将处理与数学相关的函数

from widgets import *


#   Clears all text from text box
def clear_txt():
    txt_box.config(state='normal')
    txt_box.delete('1.0', tk.END)
    txt_box.config(state='disabled')


#   Deletes 'Type here your math problem ...' to let the user add input
def delete_paceholder():
    if txt_box.get(1.0, "end-1c") == 'Type here your math problem ...':
        clear_txt()


#   Adds to the text-box what the button contains in it
def insert_btn_txt(btn):
    delete_paceholder()
    txt_box.config(state='normal')
    txt_box.insert(tk.END, btn['text'])
    txt_box.config(state='disabled')

其次,按以下方式更改gui_layout.py

from functions import *


#   Layout the standard default panel
def place_std_btns():
    i,j = 0,0
    for btn in frm_standard.children:
        frm_standard.children[btn].grid(row=j, column=i)
        i += 1
        if i == 6:
            i = 0
            j += 1


#   Make every button functional by assigning a function to it
def assign_btn_funcs():
    for btn in frm_standard.children:
        frm_standard.children[btn]['command'] = lambda x=frm_standard.children[btn]: insert_btn_txt(x)


#   Calls the layout functions above and layout the gui elements
def init_gui_layout():
    place_std_btns()
    frm_txtbox.pack()
    txt_box.pack()
    frm_standard.pack()

之后,创建一个新文件(widgets.py),并将main.py的内容移入widgets.py

最后,按如下方式调整main.py

from gui_layout import assign_btn_funcs, init_gui_layout
from widgets import win


assign_btn_funcs()
init_gui_layout()

win.mainloop()

现在我们有了继续前进的基础!

在添加更多用户界面和数学函数之前,让我们探索一些有趣的Sympy函数,以便对正在发生的事情有一个初步了解。

Image description

Image description

正如我们所见,Sympy有许多功能,例如计算积分或绘制函数。

这就是我们将要使用的库,它将使我们的工作更轻松并满足我们的程序需求。

最近,我们已经制作了标准的计算器界面,但我们还没有让它进行任何计算,所以让我们添加一些将至关重要的按钮,比如提交我们的输入和清除它。

我们将添加基本的导航按钮,所以打开widgets.py,并按照下面的代码操作

#   Navigation gui elements
frm_nav_buttons = tk.Frame(pady=8, padx=5)

btn_submit = tk.Button(master=frm_nav_buttons, text='Submit', bg='lightgray', width=5, height=2, cursor='hand2', font=('default', 11))
btn_remove = tk.Button(master=frm_nav_buttons, text='⌫', bg='lightgray', width=5, height=2, cursor='hand2', font=('default', 11))
btn_clear_txt = tk.Button(master=frm_nav_buttons, text='Clear', bg='lightgray', width=5, height=2, cursor='hand2', font=('default', 11))
btn_new_line = tk.Button(master=frm_nav_buttons, text='⤶', bg='lightgray', width=5, height=2, cursor='hand2', font=('default', 11))
btn_sci_functions= tk.Button(master=frm_nav_buttons, text='∑ⅆഽ', bg='lightgray', width=5, height=2, cursor='hand2', font=('default', 11))
btn_symbols = tk.Button(master=frm_nav_buttons, text='abc', bg='lightgray', width=5, height=2, cursor='hand2', font=('default', 11))
btn_open_image = tk.Button(master=frm_nav_buttons, text='🖼', bg='lightgray', width=5, height=2, cursor='hand2', font=('default', 11))

现在要实际渲染控件,我们需要调用pack和grid函数。

gui_layout.py更改如下

from functions import *


#   Layout the standard default panel
def place_std_btns():
    i,j = 0,0
    for btn in frm_standard.children:
        frm_standard.children[btn].grid(row=j, column=i)
        i += 1
        if i == 6:
            i = 0
            j += 1


#   Layout the main navigation panel (submit clear abc ...)
def place_nav_panel():
    txt_box.grid(row=1, column=0, sticky='new')

    i = 0
    for btn in frm_nav_buttons.children:
        frm_nav_buttons.children[btn].grid(row=0, column=i)
        i += 1


#   Make every button functional by assigning a function to it
def assign_btn_funcs():
    for btn in frm_standard.children:
        frm_standard.children[btn]['command'] = lambda x=frm_standard.children[btn]: insert_btn_txt(x)


#   Calls the layout functions above and layout the gui elements
def init_gui_layout():
    place_std_btns()
    place_nav_panel()

    frm_txtbox.pack()
    frm_nav_buttons.pack()
    frm_standard.pack()

完成这些之后,你应该会看到导航面板已填充了许多我们将稍后编程以执行有趣操作的按钮。

让我们来编程前四个按钮,看看会发生什么。

我们已经准备好了clear函数。现在我们将添加delete character功能并在functions.py中插入新行。

#   Removes a char from text box
def remove_char():
    txt_box.config(state='normal')
    txt_box.delete('end-2c', tk.END)
    txt_box.config(state='disabled')


#   Adds a new line to the text-box
def insert_new_line():
    delete_paceholder()
    txt_box.config(state='normal')
    txt_box.insert(tk.END, '\n')
    txt_box.config(state='disabled')

我添加txt_box.config(state='normal')txt_box.config(state='disabled')的原因是为了确保只有按钮能够向文本框中添加输入。我们不希望用户随意输入。

完成这些后,更新gui_layout.py中的assign_btn_funcs函数,将这些函数分配给相应的按钮。

#   Make every button functional by assigning a function to it
def assign_btn_funcs():
    for btn in frm_standard.children:
        frm_standard.children[btn]['command'] = lambda x=frm_standard.children[btn]: insert_btn_txt(x)

    btn_remove.configure(command=lambda: remove_char())
    btn_clear_txt.configure(command=lambda: clear_txt())
    btn_new_line.configure(command=lambda: insert_new_line())

现在我们能够添加、删除和清除文本框了。

我们需要编程submit按钮,这样当我们输入一些内容时,输入就会被处理,然后我们将根据输入的内容做出决定。例如,如果我们输入一个函数,该函数将被绘制;如果我们输入一个方程,则必须求解该方程;如果我们输入一些数学表达式,那么我们需要计算该表达式,依此类推……

首先,我们将添加一个函数来处理我们将在文本框中提供的原始输入。

functions.py的顶部添加以下代码

import sympy


#   Creates a graph from the input provided, might as well create numerous graphs
def plot_expression():
    exprs = process_input()
    if txt_box.index(tk.INSERT)[0] == '1':
        sympy.plot(sympy.sympify(exprs[0]), xlabel='x', ylabel='f(x)')
    if txt_box.index(tk.INSERT)[0] == '2':
        sympy.plot(sympy.sympify(exprs[0]), sympy.sympify(exprs[1]), xlabel='x', ylabel='f(x)')
    if txt_box.index(tk.INSERT)[0] == '3':
        sympy.plot(sympy.sympify(exprs[0]), sympy.sympify(exprs[1]), sympy.sympify(exprs[2]), xlabel='x', ylabel='f(x)')


#   Find the index of the last digit after char ex: √
def digit_index(expr, char): 
    start_index = expr.index(char) + 1
    index = 0
    while True:
        if expr[start_index].isdigit() or expr[start_index].isalpha():
            index = start_index
        else:
            return index
        start_index += 1


#   Remove all terms to the left side and change their signs with the equal sign removed
def process_equation(equation):
    equal_index = equation.index('=')

    expr1 = sympy.sympify(equation[:equal_index])
    expr2 = sympy.sympify(equation[equal_index + 1:])
    return expr1 - expr2


#   Remove all terms to the left side and change their signs with the inequal sign removed
def process_inequality(inequality, char):
    inequality_index = inequality.index(char)
    expr1 = sympy.sympify(inequality[:inequality_index])
    expr2 = sympy.sympify(inequality[inequality_index + 1:])
    final_expr = expr1 - expr2
    coeff = int(final_expr.coeff([x for x in final_expr.free_symbols][0]))
    if coeff < 0:
        if char == '>':
            return final_expr, '<'
        elif char == '<':
            return final_expr, '>'
        elif char == '≥':
            return final_expr, '≤'
        elif char == '≤':
            return final_expr, '≥'
    else:
        return final_expr, char


#   Adds numbers into a list and return that list
def extract_numbers(expr):
    numbers = []
    for char in expr:
        if char.isdigit():
            numbers.append(char)
    return float(''.join(numbers))


#   If the expression has a symobl say x it returns true otherwise false
def has_symbol(expr):
    try:
        right_parentheses = expr.index('(')
        left_parentheses = expr.index(')')

        for char in expr[right_parentheses + 1:left_parentheses]:
            if char.isalpha() and char != 'Ⲡ' and char != 'e':
                return True
        return False
    except:
        for char in expr:
            if char in 'abcdefghijklmnopqrstuvwxyz':
                return True
        return False


#   Seperates numbers and symbols by adding a multiplication sign 
#   so python can understand it for exapmle: (2x) becomes (2*x)
def add_star(expr):
    if 'sin' not in expr and 'cos' not in expr and 'tan' not in expr and 'cot' not in expr and 'log' not in expr:
        for i in range(len(expr)):
            if expr[i].isdigit() and expr[i + 1].isalpha()  and expr[i+1] != '°' or expr[i] == ')' and expr[i+1] == '(' and expr[i+1] != '°':
                expr = expr[:i+1] + '*' + expr[i+1:]
            if expr[i].isalpha() and expr[i + 1].isalpha()  and expr[i+1] != '°' or expr[i] == ')' and expr[i+1] == '(' and expr[i+1] != '°':
                if str(sympy.pi) not in expr:
                    expr = expr[:i+1] + '*' + expr[i+1:]
    return expr


#   Takes the raw input from the user and convert it to sympy epxression
#   so the input can be processed
def process_input():
    exprs = []

    for i in range(1, int(txt_box.index(tk.INSERT)[0]) + 1):
        expr = txt_box.get(f'{i}.0', f'{i+1}.0')
        expr = expr.replace('Ⲡ', str(sympy.pi))
        expr = expr.replace('e', str(sympy.E))
        expr = expr.replace('²', '** 2 ')
        expr = expr.replace('³', '** 3 ')
        expr = add_star(expr)
        if '(' in expr and expr[0] != '(':
            parentheses_indexes = [m.start() for m in re.finditer("\(", expr)]
            for parentheses_index in parentheses_indexes:
                if not expr[parentheses_index - 1].isalpha():
                    expr = expr[:parentheses_index] + '*' + expr[parentheses_index:]
        if '√' in expr:
            square_root_index = digit_index(expr, '√') 
            expr = expr.replace('√', '')
            expr = expr[:square_root_index] + '** 0.5 ' + expr[square_root_index:]
        if '∛' in expr:
            cube_root_index = digit_index(expr, '∛')
            expr = expr.replace('∛', '')
            expr = expr[:cube_root_index] + '** (1/3) ' + expr[cube_root_index:]
        if '°' in expr:
            deg = extract_numbers(expr)
            func = expr[:3]
            expr = f'{func}({sympy.rad(deg)})'
        if '=' in expr:
            expr = process_equation(expr)

        if '>' in str(expr):
            expr = process_inequality(expr, '>')
        elif '≥' in str(expr):
            expr = process_inequality(expr, '≥')
        elif '<' in str(expr):
            expr = process_inequality(expr, '<')
        elif '≤' in str(expr):
            expr = process_inequality(expr, '≤')

        try:    
            i_index = expr.index('i')
            if expr[i_index - 1].isdigit():
                expr = expr.replace('i', 'j')
        except:
            pass

        exprs.append(expr)
    return exprs

我们首先从一个表达式列表开始process_input函数,该列表将包含用户输入的所有行,并且表达式int(txt_box.index(tk.INSERT)[0]) + 1会将当前行数转换为整数,然后加一得到最后一行。之后,我们遍历每一行并更改每个符号,以便Python能够理解数学表达式。例如,如果我们输入了²,那么Python将无法识别该符号,所以我们将其更改为** 2。

我们需要用系数分隔每个变量,使用add star函数,它会在每个系数后添加一个乘法符号。然后我们确保括号与系数用乘法符号分隔。之后,我们将每个符号更改为可理解的符号,就像以前一样,以确保Sympy和Python能够理解,例如将根号符号更改为** 0.5。然后我们检查是否有等号或不等号,我们就调用函数将所有项移到一边以便求解。

这是我们应用程序的核心功能,它将原始数学表达式转换为Python表达式进行处理。

是时候看到结果了。所以,让我们这样做。

plot_expression函数之前添加submit函数

import re


#   Decides what action to take depending on the input
def submit():
    exprs = txt_box.get('1.0', tk.END)

    if '=' in exprs:
        compute('solve_equality')
    elif '<' in exprs or '>' in exprs or '≥' in exprs or '≤' in exprs:
        compute('solve_inequality')
    else:
        if has_symbol(exprs):
            plot_expression()
        else:
            compute('calculate_expression')

请注意,submit函数将调用compute,而compute将调用process input。

现在添加compute函数,它将返回结果并将其添加到文本中。

#   Performs a computation given the operation required then returns the result
def compute(operation):
    txt_box.config(state='normal')
    expr = process_input()[0]

    if operation == 'calculate_expression':
        result = f'{round(float(sympy.sympify(expr)), 2)}'
    elif operation == 'solve_equality':
        exprs = process_input()
        solutions = None
        if len(exprs) == 1:
            solutions = sympy.solve(sympy.sympify(exprs[0]))
            if len(solutions) == 1:
                solutions = solutions[0]
        elif len(exprs) == 2:
            solutions = sympy.solve((sympy.sympify(exprs[0]), sympy.sympify(exprs[1])))

        result = solutions
    elif operation == 'solve_inequality':
        symbol = [symbol for symbol in sympy.solve(expr[0], dict=True)[0].items()][0][0]
        solution = [symbol for symbol in sympy.solve(expr[0], dict=True)[0].items()][0][1]
        result = f'{symbol}{expr[1]}{solution}'
    elif operation == 'factor_expression':
        result = sympy.sympify(expr).factor()
    elif operation == 'expand_expression':
        result = sympy.sympify(expr).expand()
    elif operation == 'absolute':
        result = abs(int(sympy.sympify(expr)))    
    elif operation == 'limit':
        value = ent_limit_value.get()
        value = value.replace('∞', str(sympy.S.Infinity))
        limit = sympy.Limit(sympy.sympify(expr), sympy.Symbol('x'), sympy.sympify(value)).doit()
        result = limit
    elif operation == 'derivative':
        derivative = sympy.Derivative(sympy.sympify(expr), sympy.Symbol('x')).doit()
        result = derivative
    elif operation == 'integral':
        integral = sympy.Integral(sympy.sympify(expr), sympy.Symbol('x')).doit()
        result = integral
    elif operation == 'summation':
        x = sympy.Symbol('x')
        summation = sympy.summation(sympy.sympify(expr), (x, sympy.sympify(ent_summation_start.get()), sympy.sympify(ent_summation_n.get())))
        result = summation

    txt_box.insert(tk.END, f'\n{result}')
    txt_box.config(state='disabled')

现在运行应用程序,尝试输入一些内容,看看会发生什么。

很有成就感,不是吗?

话虽如此,还有一些事情要做。

让我们为程序添加科学函数面板,这样当我们点击∑ⅆഽ时,它就会显示面板。

打开widgets.py并在文件末尾添加以下代码

#   Scientific mode gui elements
frm_sci = tk.LabelFrame(text='Sci', font=('default', 12))

lbl_trigonometry = tk.Label(master=frm_sci, text='Trigonometry:', font=('default', 12))
lbl_inequality = tk.Label(master=frm_sci, text='Inequality:', width=8, height=1, font=('default', 12))
lbl_calculus = tk.Label(master=frm_sci, text='Calculus:', width=8, height=1, font=('default', 12))
lbl_log = tk.Label(master=frm_sci, text='Log:', width=4, height=1, font=('default', 12))
lbl_other = tk.Label(master=frm_sci, text='Other:', width=4, height=1, font=('default', 12))


frm_trig = tk.Frame(master=frm_sci, pady=8)

deg_type_choice = tk.IntVar()
btn_sin = tk.Button(master=frm_trig, text='sin', width=5, height=1, font=('default', 12),  cursor='hand2')
btn_cos = tk.Button(master=frm_trig, text='cos', width=5, height=1, font=('default', 12), cursor='hand2')
btn_tan = tk.Button(master=frm_trig, text='tan', width=5, height=1, font=('default', 12), cursor='hand2')
btn_cot = tk.Button(master=frm_trig, text='cot', width=5, height=1, font=('default', 12), cursor='hand2')
btn_degree = tk.Button(master=frm_trig, text='°', width=5, height=1, font=('default', 12), cursor='hand2')


frm_inequality = tk.Frame(master=frm_sci, pady=8)

btn_greater = tk.Button(master=frm_inequality, text='>', width=5, height=1, font=('default', 12), cursor='hand2')
btn_greater_equal = tk.Button(master=frm_inequality, text='≥', width=5, height=1, font=('default', 12), cursor='hand2')
btn_less = tk.Button(master=frm_inequality, text='<', width=5, height=1, font=('default', 12), cursor='hand2')
btn_less_equal = tk.Button(master=frm_inequality, text='≤', width=5, height=1, font=('default', 12), cursor='hand2')


frm_calculus = tk.Frame(master=frm_sci, pady=8)

btn_limit = tk.Button(master=frm_calculus ,text='Limit:\n x-->', width=5, height=1, font=('default', 12), cursor='hand2')
ent_limit_value = tk.Entry(master=frm_calculus, width=5, font=('default', 12))
btn_insert_infinity = tk.Button(master=frm_calculus, text='∞', width=5, height=1, font=('default', 12), cursor='hand2')
btn_derivative = tk.Button(master=frm_calculus, text='ⅆ', width=5, height=1, font=('default', 12), cursor='hand2')
btn_integral = tk.Button(master=frm_calculus, text='⎰', width=5, height=1, font=('default', 12), cursor='hand2')


frm_log = tk.Frame(master=frm_sci, pady=8)

base_choice = tk.IntVar()
btn_log = tk.Button(master=frm_log, text='log', width=5, height=1, font=('default', 12))
lbl_base = tk.Label(master=frm_log, text='Base:', width=5, height=1, font=('default', 12))
ent_base = tk.Entry(master=frm_log,  width=5, font=('default', 12))
btn_e = tk.Button(master=frm_log, text='e', width=5, height=1, font=('default', 12), cursor='hand2')


frm_expand_factor = tk.Frame(master=frm_sci, pady=8)

btn_expand = tk.Button(master=frm_expand_factor, text='Expand', bg='white', width=6, height=1, font=('default', 12), cursor='hand2')
btn_factor = tk.Button(master=frm_expand_factor, text='Factor', bg='white', width=6, height=1, font=('default', 12), cursor='hand2')


frm_other_sci = tk.Frame(master=frm_sci, pady=8)

ent_summation_n = tk.Entry(master=frm_other_sci, width=5, font=('default', 12))
btn_summation = tk.Button(master=frm_other_sci, text='∑',  width=5, height=1, font=('default', 12), cursor='hand2')
btn_absolute = tk.Button(master=frm_other_sci, text='| |',  width=5, height=1, font=('default', 12), cursor='hand2')
btn_imag = tk.Button(master=frm_other_sci, text='I',  width=5, height=1, font=('default', 12), cursor='hand2')
btn_factorial = tk.Button(master=frm_other_sci, text='!',  width=5, height=1, font=('default', 12), cursor='hand2')
ent_summation_start = tk.Entry(master=frm_other_sci, width=5, font=('default', 12))

现在,要将函数映射到按钮,请在assign_btn_function的末尾添加如下代码

   for btn in frm_trig.children:
        frm_trig.children[btn]['command'] = lambda x=frm_trig.children[btn]: insert_btn_txt(x)

    btn_log.configure(command=lambda: insert_btn_txt(btn_log))

    btn_e.configure(command=lambda: insert_btn_txt(btn_e))
    btn_factorial.configure(command=lambda: insert_btn_txt(btn_factorial))
    btn_absolute.configure(command=lambda: compute('absolute'))
    btn_imag.configure(command=lambda: insert_btn_txt(btn_imag))
    btn_derivative.configure(command=lambda: compute('derivative'))
    btn_integral.configure(command=lambda: compute('integral'))

    btn_greater.configure(command=lambda: insert_btn_txt(btn_greater))
    btn_greater_equal.configure(command=lambda: insert_btn_txt(btn_greater_equal))
    btn_less.configure(command=lambda: insert_btn_txt(btn_less))
    btn_less_equal.configure(command=lambda: insert_btn_txt(btn_less_equal))

    btn_remove.configure(command=lambda: remove_char())
    btn_clear_txt.configure(command=lambda: clear_txt())
    btn_new_line.configure(command=lambda: insert_new_line())
    btn_sci_functions.configure(command=lambda: show_hide_sci_functions())
    btn_symbols.configure(command=lambda: show_hide_symbols())
    btn_open_image.config(command=lambda: read_from_image(open_file()))

    btn_expand.configure(command=lambda: compute('expand_expression'))
    btn_factor.configure(command=lambda: compute('factor_expression'))

    btn_limit.configure(command=lambda: compute('limit'))
    btn_insert_infinity.configure(command=lambda: ent_limit_value.insert(tk.END, '∞'))

    btn_summation.configure(command=lambda: compute('summation'))aster=frm_other_sci, text='I',  width=5, height=1, font=('default', 12), cursor='hand2')
    btn_factorial = tk.Button(master=frm_other_sci, text='!',  width=5, height=1, font=('default', 12), cursor='hand2')
    ent_summation_start = tk.Entry(master=frm_other_sci, width=5, font=('default', 12))

添加此函数以按正确的顺序放置元素

#   Layout the functions panel (sin cos tan ...)
def place_sci_func_btns():
    ent_summation_n.grid(row=0, column=0)
    btn_summation.grid(row=1, column=0)
    btn_absolute.grid(row=1, column=1)
    btn_imag.grid(row=1, column=2)
    btn_factorial.grid(row=1, column=3)
    ent_summation_start.grid(row=2, column=0)

    i = 0
    for btn in frm_calculus.children:
        frm_calculus.children[btn].grid(row=0, column=i)
        i += 1

    i = 0
    for btn in frm_expand_factor.children:
        frm_expand_factor.children[btn].grid(row=0, column=i, padx=4)
        i += 1

    i = 0
    for btn in frm_log.children:
        frm_log.children[btn].grid(row=0, column=i)
        i += 1

    i = 0
    for btn in frm_trig.children:
        frm_trig.children[btn].grid(row=0, column=i)
        i += 1

    i = 0
    for btn in frm_inequality.children:
        frm_inequality.children[btn].grid(row=0, column=i)
        i += 1

更新init_gui_layout以实际渲染gui组件

#   Calls the layout functions above and layout the gui elements
def init_gui_layout():
    place_nav_panel()
    place_std_btns()
    place_sci_func_btns()

    frm_txtbox.pack()
    frm_nav_buttons.pack()
    frm_standard.pack()

    lbl_trigonometry.pack()
    frm_trig.pack()
    lbl_inequality.pack()
    frm_inequality.pack()
    lbl_calculus.pack()
    frm_calculus.pack()
    lbl_log.pack()
    frm_log.pack()
    lbl_other.pack()
    frm_other_sci.pack()
    frm_expand_factor.pack()

最后,在functions.py中添加此函数以显示和隐藏科学函数面板。

#   Triggers the functions panel
#   Ex: If it is visible it will hide it
def show_hide_sci_functions():
    if frm_sci.winfo_ismapped():
        frm_standard.pack()
        frm_sci.pack_forget()
        frm_symbols.pack_forget()
    else:
        frm_standard.pack_forget()
        frm_symbols.pack_forget()
        frm_sci.pack()

现在你应该能够使用科学函数了。

同样的方式,我们将编程符号按钮。

首先,添加符号控件,即字母a到z

#   Symbols mode gui elements
frm_symbols = tk.LabelFrame(text='Symbols', font=('default', 12))

#   Generating buttons from a to z using list comprehension and chr()
symbol_btns = [tk.Button(master=frm_symbols, text=chr(i), width=5, height=2, cursor='hand2', font=('default', 12))
                for i in range(97, 123)]

要映射符号按钮,请在assign_btn_function的末尾添加如下代码

    btn_symbols.configure(command=lambda: show_hide_symbols())
    for btn in frm_symbols.children:
        frm_symbols.children[btn]['command'] = lambda x=frm_symbols.children[btn]: insert_btn_txt(x)

现在添加place_symbols_btns以按正确的顺序放置元素

#   Layout the symbols panel 
def place_symbols_btns():
    i, j = 0, 0
    for btn in frm_symbols.children:
        frm_symbols.children[btn].grid(row=j, column=i)
        i += 1
        if i % 10 == 0:
            j += 1
            i = 0

最后,通过添加show_hide_symbols function来触发符号面板并更新show_hide_sci_functions:来更新functions.py。

#   Triggers the symobls panel
#   Ex: If it is visible it will hide it
def show_hide_symbols():
    if frm_symbols.winfo_ismapped():
        frm_standard.pack()
        frm_symbols.pack_forget()
        frm_sci.pack_forget()
    else:
        frm_symbols.pack()
        frm_standard.pack_forget()
        frm_sci.pack_forget() 


#   Triggers the functions panel
#   Ex: If it is visible it will hide it
def show_hide_sci_functions():
    if frm_sci.winfo_ismapped():
        frm_standard.pack()
        frm_sci.pack_forget()
        frm_symbols.pack_forget()
    else:
        frm_standard.pack_forget()
        frm_symbols.pack_forget()
        frm_sci.pack()

最后,在gui_layout.py中更新init_gui_layout函数

#   Calls the layout functions above and layout the gui elements
def init_gui_layout():
    place_nav_panel()
    place_std_btns()
    place_sci_func_btns()
    place_symbols_btns()

    frm_txtbox.pack()
    frm_nav_buttons.pack()
    frm_standard.pack()

    lbl_trigonometry.pack()
    frm_trig.pack()
    lbl_inequality.pack()
    frm_inequality.pack()
    lbl_calculus.pack()
    frm_calculus.pack()
    lbl_log.pack()
    frm_log.pack()
    lbl_other.pack()
    frm_other_sci.pack()
    frm_expand_factor.pack()

几乎完成了!现在我们需要编程最后一个按钮,它将把图像上的文本转换为Python可以理解的字符串。

转到function.py并添加以下函数

def open_file():
    filetypes = (
        ('Images files', '*.png'),
        ('All files', '*.*')
    )
    file_path = fd.askopenfile(filetypes=filetypes).name
    return file_path


#   Read text from image given the image path
#   If text is clear then returns the text as string
def read_from_image(image_path):
    from PIL import Image 
    from pytesseract import pytesseract 
    # Defining paths to tesseract.exe 
    # and the image we would be using 
    path_to_tesseract = r"C:\Program Files\Tesseract-OCR\tesseract.exe"
    # image_path = r"test.png"

    # Opening the image & storing it in an image object 
    img = Image.open(image_path) 

    # Providing the tesseract executable 
    # location to pytesseract library 
    pytesseract.tesseract_cmd = path_to_tesseract 

    # Passing the image object to image_to_string() function 
    # This function will extract the text from the image 
    text = pytesseract.image_to_string(img) 

    delete_paceholder()
    # Displaying the extracted text
    txt_box.config(state='normal')
    txt_box.insert(tk.END, text[:-1])
    txt_box.config(state='disabled')

并添加下面这行

from tkinter import filedialog as fd

它将从Tkinter导入文件对话框以获取图像路径。

总结

恭喜,干得好!希望我的文章对您有所帮助。如果您有任何建议,我将很乐意回复您。

感谢您的时间,请保重。

要发现更多有趣的项目,请点击此处

历史

  • 2023年10月30日:初始版本
© . All rights reserved.