3
\$\begingroup\$

This program takes x numbers and displays the mean, median and mode.

My goal here was to use classes and class properties the best I could to make the code shorter (honestly i may have just made it longer), so I would like to get some feedback on that.

import tkinter as tk
from typing import Callable, List
from statistics import mean, median, mode

"""MainFunctions = initialization creates 2 starting input boxes and the 3 outputs"""
"""Input = initialization creates an entry and stringvar for input"""
"""Output = initialization creates a label for the name of the average and another for the output"""


class Input:
    def __init__(self, parent: tk.Tk, index: int, callback: Callable[[], None]) -> None:
        self.value_var = tk.StringVar(parent)
        self.value_var.trace_add(mode="write", callback=callback)

        self.value_entry = tk.Entry(
            parent, textvariable=self.value_var
        )
        self.value_entry.grid(row=index, column=1)

    def delete_entry(self) -> None:
        self.value_entry.destroy()

    @property
    def value(self) -> str:
        if self.value_var.get():
            return self.value_var.get()
        else:
            return None

    @property
    def row(self) -> int:
        grid_info = self.value_entry.grid_info()
        return grid_info['row']


class Output:
    def __init__(self, parent: tk.Tk, index: int, name: str) -> None:
        self.name = name

        self.name_label = tk.Label(parent, text=f"{name}:")
        self.name_label.grid(row=index, column=0)

        self.output_label = tk.Label(parent, text=0)
        self.output_label.grid(row=index, column=1)

    def grid(self, row) -> None:
        self.name_label.grid(row=row, column=0)
        self.output_label.grid(row=row, column=1)

    def set_output(self, values) -> None:
        try:
            match self.name:
                case "Mean": self.output_label.configure(text=mean(values))
                case "Median": self.output_label.configure(text=median(values))
                case "Mode": self.output_label.configure(text=mode(values))
        except:
            self.output_label.configure(text=0)


class MainFunctions:
    def __init__(self, parent: tk.Tk) -> None:
        self.parent = parent

        self.inputs = [
            Input(self.parent, 0, self.input_callback),
            Input(self.parent, 1, self.input_callback)
        ]

        self.outputs = [
            Output(
                self.parent, 2, "Mean"
            ),
            Output(
                self.parent, 3, "Median"
            ),
            Output(
                self.parent, 4, "Mode"
            )
        ]

    def create_destroy_input(self) -> None:
        """Counts the number of empty stringvars"""
        empty_stringvars = 0

        for input in self.inputs[::-1]:
            if input.value == None:
                empty_stringvars += 1

        """Deletes all empty entries and stringvars except for 1, then creates another"""
        for input in self.inputs[::-1]:
            if empty_stringvars <= 1:
                self.inputs += [
                    Input(
                        self.parent, len(self.inputs), self.input_callback
                    )
                ]
                return
            if input.value == None:
                input.delete_entry()
                self.inputs.remove(input)
                empty_stringvars -= 1

    def grid_output(self) -> None:
        """To grid the output according to the how many entries there are"""
        row = 1

        for output in self.outputs:
            output.grid(row + len(self.inputs))
            row += 1

    def get_values(self) -> List:
        """Gets all values in entries"""
        values = []

        for input in self.inputs:
            try:
                value = input.value.replace(",", ".")
                values.append(float(value))
            except:
                pass

        return values

    def set_output(self, values) -> None:
        """Calls function for setting output 3 times for each output label"""
        for output in self.outputs:
            output.set_output(values)

    def input_callback(self, name: str, mode: str, index: str) -> None:
        """Every stringvar calls back to this"""
        self.create_destroy_input()
        self.grid_output()
        self.set_output(self.get_values())


def main() -> None:
    root = tk.Tk()
    root.title("Mean, median and mode calculator")
    root.resizable(width=False, height=False)
    MainFunctions(root)
    root.mainloop()


if __name__ == "__main__":
    main()

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

GUI

When I run the code, the Tk window is too narrow, and I can't see full window title.

Also, it would be helpful to display some simple instructions for the user.

Input checking

When I enter a word instead of a number, it silently accepts it. Consider displaying some kind of message about invalid input.

Documentation

It is great that you have a docstring to describe each class, but it would be better to place them closer to their classes instead of at the top of the code.

Add a docstring to the top of the code to summarize the code's purpose:

"""
Calculate statistics for a list of numbers.
Launches a GUI window where the user can enter numbers.
"""

It is great that you added plenty of docstrings like:

    """Counts the number of empty stringvars"""

However, it is not clear what you mean by "stringvars". A brief explanation would be helpful.

Naming

Since input is a Python keyword, it would be best to avoid using it as a variable name in the MainFunctions class:

    for input in self.inputs[::-1]:

Perhaps name the variable as user_input:

    for user_input in self.inputs[::-1]:

The class name MainFunctions is not very descriptive. Consider a name which is more specific to what is does.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ For shadowing input(), PEP 8 recommends "single_trailing_underscore_: used by convention to avoid conflicts with Python keyword", which would give us input_. \$\endgroup\$ Commented Nov 25, 2024 at 5:12

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.