Оптимизация кода калькулятора

Есть код. В классе App прописана форма и калькулятор. Решил вынести часть кода калькулятора #логика в отдельный класс, и оставить часть кода, которая просто создаёт кнопки через цикл. Но при попытки сделать это столкнулся с множественными ошибками. Выходит, или я делаю то, что не должен, или не понимаю как правильно пользоваться этим.

Как я пробовал сделать:

Создаём отдельный класс типа class Scal:. Пробовал также добавить @staticmethod, но это никак не помогло. Вставляем в него "логику", кнопки оставляем в форме где они и должны быть. Поскольку calc_entry остаётся в классе App, то дописываю везде App()., так же как и перед tab3.

В эту же строку добавлял cmd = lambda x=i: >>Scal().<<calc(x)

Вопрос думаю понятен: как разделить данный код и можно ли? Так же буду признателен за какую-либо иную оптимизацию кода<3

Делюсь исходно рабочим кодом:

import numpy as np
import pandas as pd
from tkinter import *
import tkinter as tk
from tkinter import ttk
import tkinter.messagebox as mb

class App(tk.Tk):
    def __init__(self):
        super().__init__()
        #self = Tk()
        self.title("Матрицы для детей")
        self.geometry('745x250')

        tab_control = ttk.Notebook(self)
        tab1 = ttk.Frame(tab_control)
        tab2 = ttk.Frame(tab_control)
        tab3 = ttk.Frame(tab_control)

        tab_control.add(tab1, text='Матрицы')
        tab_control.add(tab2, text='Вектора')
        tab_control.add(tab3, text='Скаляры')

        tab_control.pack(expand=1, fill='both')

        self.spin0 = Spinbox(tab1, from_=0, to=9, width=5)
        self.spin0.grid(column=0, padx=25, row=0)

        self.spin1 = Spinbox(tab1, from_=0, to=9, width=5)
        self.spin1.grid(column=1, padx=50, row=0)

        self.spin2 = Spinbox(tab1, from_=0, to=9, width=5)
        self.spin2.grid(column=2, padx=70, row=0)

        #self.spin0_0 = Spinbox(tab2, from_=0, to=9, width=5)
        #self.spin0_0.grid(column=1, padx=0, row=0)

        #self.spin0_1 = Spinbox(tab2, from_=0, to=9, width=5)
        #self.spin0_1.grid(column=2, padx=25, row=0)

        ### матрицы
        btn = Button(tab1, text='Матричное произведение', width=25, command=Matrix().martix_multiply)
        btn.grid(row=2, column=0, padx=0, pady=5)

        btn = Button(tab1,  text='Сложение', width=25, command=Matrix().summ)
        btn.grid(row=2, column=1, padx=0, pady=5)

        btn = Button(tab1,  text='Матрица на скаляр', width=25, command=Matrix().on_scal)
        btn.grid(row=2, column=2, padx=0, pady=5)

        btn = Button(tab1,  text='Вектор на матрицу', width=25, command=Matrix().vector)
        btn.grid(row=2, column=3, padx=0, pady=5)

        btn = Button(tab1, text='Поэлементное произведение', width=25, command=Matrix().multiply)
        btn.grid(row=3, column=0, padx=0, pady=5)

        btn = Button(tab1,  text='След и определитель', width=25, command=Matrix().opredelitel)
        btn.grid(row=3, column=1, padx=0, pady=5)

        btn = Button(tab1,  text='Обратная матрица', width=25, command=Matrix().inv_matrix)
        btn.grid(row=3, column=2, padx=0, pady=5)

        btn = Button(tab1,  text='Транспонирование', width=25, command=Matrix().transposition)
        btn.grid(row=3, column=3, padx=0, pady=5)

        ### вектора
        btn = Button(tab2, text='Вектор на скаляр', width=25, command=Vector().vector_on_scal)
        btn.grid(row=2, column=0, padx=0, pady=5)

        btn = Button(tab2,  text='Поэлементное Сложение', width=25, command=Vector().summ_vector)
        btn.grid(row=2, column=1, padx=0, pady=5)

        btn = Button(tab2,  text='Поэлементное Умножение', width=25, command=Vector().mul_vector)
        btn.grid(row=2, column=2, padx=0, pady=5)

        btn = Button(tab2,  text='Вектор на матрицу', width=25, command=Matrix().vector)
        btn.grid(row=2, column=3, padx=0, pady=5)

        btn = Button(tab2, text='Скалярное произведение', width=25, command=Vector().scal_mul)
        btn.grid(row=3, column=0, padx=0, pady=5)

        btn = Button(tab2,  text='Векторное произведение', width=25, command=Vector().mull_vect)
        btn.grid(row=3, column=1, padx=0, pady=5)

        btn = Button(tab2,  text='Длина вектора', width=25, command=Vector().length_vect)
        btn.grid(row=3, column=2, padx=0, pady=5)

        btn = Button(tab2,  text='Проверка сонаправленности', width=25, command=0)
        btn.grid(row=3, column=3, padx=0, pady=5)

        btn = Button(tab2,  text='Проверка на ортогональность', width=25, command=0)
        btn.grid(row=4, column=0, padx=0, pady=5)

        # логика
        def calc(key):
            global memory
            if key == "=":
                # исключение написания слов
                str1 = "-+0123456789.*/)("
                if calc_entry.get()[0] not in str1:
                    calc_entry.insert(END, "First symbol is not number!")
                    mb.showerror("Error!", "You did not enter the number!")
                # исчисления
                try:
                    result = eval(calc_entry.get())
                    calc_entry.insert(END, "=" + str(result))
                except:
                    calc_entry.insert(END, "Error!")
                    mb.showerror("Error!", "Check the correctness of data")

            # очищение поля ввода
            elif key == "C":
                calc_entry.delete(0, END)

            elif key == "±":
                if "=" in calc_entry.get():
                    calc_entry.delete(0, END)
                try:
                    if calc_entry.get()[0] == "-":
                        calc_entry.delete(0)
                    else:
                        calc_entry.insert(0, "-")
                except IndexError:
                    pass

            elif key == "π":
                calc_entry.insert(END, math.pi)

            elif key == "Exit":
                tab3.after(1, App.tab3.destroy)
                sys.exit

            elif key == "xⁿ":
                calc_entry.insert(END, "**")

            elif key == "sin":
                calc_entry.insert(END, "=" + str(math.sin(int(calc_entry.get()))))

            elif key == "cos":
                calc_entry.insert(END, "=" + str(math.cos(int(calc_entry.get()))))

            elif key == "(":
                calc_entry.insert(END, "(")

            elif key == ")":
                calc_entry.insert(END, ")")

            elif key == "n!":
                calc_entry.insert(END, "=" + str(math.factorial(int(calc_entry.get()))))

            elif key == "√2":
                calc_entry.insert(END, "=" + str(math.sqrt(int(calc_entry.get()))))

            else:
                if "=" in calc_entry.get():
                    calc_entry.delete(0, END)
                calc_entry.insert(END, key)

        ### кнопки
        bttn_list = [
            "7", "8", "9", "+", "*",
            "4", "5", "6", "-", "/",
            "1", "2", "3", "=", "xⁿ",
            "0", ".", "±", "C",
            "Exit", "π", "sin", "cos",
            "(", ")", "n!", "√2", ]
        r = 1
        c = 0
        for i in bttn_list:
            rel = ""
            cmd = lambda x=i: calc(x)
            ttk.Button(tab3, text=i, command=cmd, width=10).grid(row=r, column=c)
            c += 1
            if c > 4:
                c = 0
                r += 1

        calc_entry = Entry(tab3, width=33)
        calc_entry.grid(row=0, column=0, columnspan=5)
      
if __name__ == "__main__":
    read_a = pd.read_csv('first.csv', sep=";", header=None)
    read_b = pd.read_csv('second.csv', sep=";", header=None)
    app = App()
    app.mainloop()

Ответы (1 шт):

Автор решения: insolor
  • Первый очевидный шаг - вытащить функцию calc из __init__, сделать для начала отдельным методом того же класса, чтобы в __init__ осталось только создание графического интерфейса.

    Вложенными удобно делать небольшие функции, но такие огромные функции вложенными лучше не делать: получается, что, текст метода __init__ внезапно прерывается огромной функцией calc, а после нее идет еще достаточно большой кусок метода. Читать такой код очень неудобно.

  • Можно пообъединять if-ы с calc_entry.insert(END, какой-то символ), чтобы получилось примерно так:

    elif key in {"(", ")", ...}:
        calc_entry.insert(END, key)
    
  • Для функций можно сделать словарь, где набору символов соответствует конкретная функция, по нажатой клавише выбирать нужную и вычислять выражение:

    funcs = {"sin": math.sin, "cos": math.cos, ...}
    
    ...
    
    elif key in funcs:
       calc_entry.insert(END, "=" + str(funcs[key](int(calc_entry.get()))))
    
  • Создание кнопок для матриц и векторов можно делать циклами - по аналогии с созданием кнопок из bttn_list, только в этом же списке можно хранить надпись кнопки и ее обработчик, например ('Матричное произведение', Matrix().martix_multiply)

  • Если перешли к ООП, то не используйте global - записываете значения в поля объекта

  • Метод calc можно попробовать абстрагировать от графического интерфейса (чтобы можно было вытащить из класса App), например передавать в него текст из текстового поля, и возвращать из него новый текст. Обработку нажатия на кнопку выхода либо вытащить из calc, либо бросать исключение какого-то своего типа, снаружи отлавливать и обрабатывать.

  • Создание и заполнение каждого отдельного таба можно выделить в отдельные классы

Пример с вынесенным наружу calc (куски кода, касающиеся заполнения первых двух табов, вырезал, чтобы не мешали пониманию):

from tkinter import *
import tkinter as tk
from tkinter import ttk
import tkinter.messagebox as mb
import math


functions = {
    "sin": math.sin,
    "cos": math.cos,
    "n!": math.factorial,
    "√2": math.sqrt
}


# логика
def calc(key, text):
    if key == "=":
        # исчисления
        result = eval(text)
        return text + "=" + str(result)
    # очищение поля ввода
    elif key == "C":
        return ""

    elif key == "±":
        if "=" in text:
            return ""
        try:
            if text[0] == "-":
                return text[1:]
            else:
                return "-" + text
        except IndexError:
            pass

    elif key == "π":
        return text + math.pi
        
    elif key == "xⁿ":
        return text + "**"

    elif key in functions:
        return text + "=" + str(functions[key](int(text)))

    # elif key in {"(", ")"}:  # Будет обработано в else
    #     return text + key

    else:
        if "=" in text:
            text = ""

        return text + key


class App(tk.Tk):
    def __init__(self):
        super().__init__()
        #self = Tk()
        self.title("Матрицы для детей")
        self.geometry('745x250')

        tab_control = ttk.Notebook(self)
        tab1 = ttk.Frame(tab_control)
        tab2 = ttk.Frame(tab_control)
        tab3 = ttk.Frame(tab_control)

        tab_control.add(tab1, text='Матрицы')
        tab_control.add(tab2, text='Вектора')
        tab_control.add(tab3, text='Скаляры')

        tab_control.pack(expand=1, fill='both')

        self.spin0 = Spinbox(tab1, from_=0, to=9, width=5)
        self.spin0.grid(column=0, padx=25, row=0)

        self.spin1 = Spinbox(tab1, from_=0, to=9, width=5)
        self.spin1.grid(column=1, padx=50, row=0)

        self.spin2 = Spinbox(tab1, from_=0, to=9, width=5)
        self.spin2.grid(column=2, padx=70, row=0)

        calc_entry = Entry(tab3, width=33)
        calc_entry.grid(row=0, column=0, columnspan=5)

        ### кнопки
        bttn_list = [
            "7", "8", "9", "+", "*",
            "4", "5", "6", "-", "/",
            "1", "2", "3", "=", "xⁿ",
            "0", ".", "±", "C",
            "Exit", "π", "sin", "cos",
            "(", ")", "n!", "√2", ]
        r = 1
        c = 0
        for i in bttn_list:
            rel = ""
            
            def command(key=i):
                if key == "Exit":
                    tab3.after(1, App.tab3.destroy)
                    sys.exit(0)
                
                try:
                    result = calc(key, calc_entry.get())
                except Exception as ex:
                    mb.showerror("Error!", "Check the correctness of data")
                    raise
                else:
                    calc_entry.delete(0, tk.END)
                    calc_entry.insert(0, result)
            
            ttk.Button(tab3, text=i, command=command, width=10).grid(row=r, column=c)
            c += 1
            if c > 4:
                c = 0
                r += 1


if __name__ == "__main__":
    app = App()
    app.mainloop()
→ Ссылка