I've been implementing a simple Python calculator for learning purposes. Since there are no properties or bindings like in Javafx, I implemented my own MVC pattern. I'm aware of missing logic like checking for division by zero and stuff, but I wanted to keep it simple for this review.
So there are some questions:
- Is the MVC pattern okay this way?
- What's the best way to check passed parameters of methods? (see Model.digit(int))
- Is the visibility of fields okay this way?
- Are the imports organized correctly?
import tkinter as tk
from tkinter import *
import tkinter.font
from abc import ABC, abstractmethod
from enum import IntEnum
class Observer(ABC):
@abstractmethod
def notify(self):
pass
class Observable():
def __init__(self):
self._observers = set()
def register(self, observer: Observer):
self._observers.add(observer)
def notify_all(self):
for observer in self._observers:
observer.notify()
class Operator(IntEnum):
NONE = 0
EQUALS = 1
PLUS = 10
MINUS = 11
MULT = 12
DIV = 13
class Model(Observable):
def __init__(self):
Observable.__init__(self);
self._temp = 0
self._result = 0
self._operator = Operator.NONE
def digit(self, digit: int):
if digit is None:
raise TypeError
if self._operator == Operator.EQUALS:
self.reset()
self._temp = self._temp * 10 + digit
self.notify_all()
def operator(self, operator: Operator):
if operator == Operator.EQUALS:
if self._operator == Operator.PLUS:
self._temp += self._result
self._operator = Operator.EQUALS
elif self._operator == Operator.MINUS:
self._temp = self._result - self._temp
self._operator = Operator.EQUALS
elif self._operator == Operator.MULT:
self._temp *= self._result
self._operator = Operator.EQUALS
elif self._operator == Operator.DIV:
self._temp = self._result / self._temp
self._operator = Operator.EQUALS
elif Operator.PLUS <= operator <= Operator.DIV:
self._result = self._temp
self._temp = 0
self._operator = operator
else:
raise NotImplementedError("unexpected enum")
self.notify_all()
def reset(self):
self._temp = 0
self._result = 0
self._operator = Operator.NONE
self.notify_all()
class Controller():
def __init__(self, model: Model):
self._model = model
def digit(self, digit: int):
self._model.digit(digit)
def operator(self, operator: Operator):
self._model.operator(operator)
class View(tk.Tk, Observer):
CONST_TITLE = "Calculator"
CONST_GEOMETRY = "300x400"
def __init__(self):
tk.Tk.__init__(self)
self.title(self.CONST_TITLE)
self.geometry(self.CONST_GEOMETRY)
self._model = Model()
self._model.register(self)
self._controller = Controller(self._model)
self._frame = tk.Frame(self, bg="white")
self._frame.pack(fill=BOTH, expand=1)
for row in range(6):
self._frame.rowconfigure(row, weight=1)
for column in range(4):
self._frame.columnconfigure(column, weight=1)
self._font = tkinter.font.Font(root=self._frame, family="Helvetica", size="30", weight=tkinter.font.BOLD)
self._text = tk.Label(self._frame, text="INIT", font=self._font, justify=RIGHT, anchor=E, bg="white", padx=20, pady=20)
self._text.grid(row="0", column="0", columnspan="4", sticky="NSWE")
self._button_0 = tk.Button(self._frame, text="0", font=self._font, command=lambda:self._controller.digit(0))
self._button_0.grid(row="5", column="0", columnspan="2", sticky="NSWE")
self._button_1 = tk.Button(self._frame, text="1", font=self._font, command=lambda:self._controller.digit(1))
self._button_1.grid(row="4", column="0", sticky="NSWE")
self._button_2 = tk.Button(self._frame, text="2", font=self._font, command=lambda:self._controller.digit(2))
self._button_2.grid(row="4", column="1", sticky="NSWE")
self._button_3 = tk.Button(self._frame, text="3", font=self._font, command=lambda:self._controller.digit(3))
self._button_3.grid(row="4", column="2", sticky="NSWE")
self._button_4 = tk.Button(self._frame, text="4", font=self._font, command=lambda:self._controller.digit(4))
self._button_4.grid(row="3", column="0", sticky="NSWE")
self._button_5 = tk.Button(self._frame, text="5", font=self._font, command=lambda:self._controller.digit(5))
self._button_5.grid(row="3", column="1", sticky="NSWE")
self._button_6 = tk.Button(self._frame, text="6", font=self._font, command=lambda:self._controller.digit(6))
self._button_6.grid(row="3", column="2", sticky="NSWE")
self._button_7 = tk.Button(self._frame, text="7", font=self._font, command=lambda:self._controller.digit(7))
self._button_7.grid(row="2", column="0", sticky="NSWE")
self._button_8 = tk.Button(self._frame, text="8", font=self._font, command=lambda:self._controller.digit(8))
self._button_8.grid(row="2", column="1", sticky="NSWE")
self._button_9 = tk.Button(self._frame, text="9", font=self._font, command=lambda:self._controller.digit(9))
self._button_9.grid(row="2", column="2", sticky="NSWE")
self._button_plus = tk.Button(self._frame, text="+", font=self._font, command=lambda:self._controller.operator(Operator.PLUS))
self._button_plus.grid(row="2", column="3", rowspan="2", sticky="NSWE")
self._button_plus = tk.Button(self._frame, text="-", font=self._font, command=lambda:self._controller.operator(Operator.MINUS))
self._button_plus.grid(row="1", column="3", sticky="NSWE")
self._button_plus = tk.Button(self._frame, text="*", font=self._font, command=lambda:self._controller.operator(Operator.MULT))
self._button_plus.grid(row="1", column="2", sticky="NSWE")
self._button_plus = tk.Button(self._frame, text="/", font=self._font, command=lambda:self._controller.operator(Operator.DIV))
self._button_plus.grid(row="1", column="1", sticky="NSWE")
self._button_equals = tk.Button(self._frame, text="=", font=self._font, command=lambda:self._controller.operator(Operator.EQUALS))
self._button_equals.grid(row="4", column="3", rowspan="2", sticky="NSWE")
self._button_clear = tk.Button(self._frame, text="C", font=self._font, command=self._model.reset)
self._button_clear.grid(row="1", column="0", sticky="NSWE")
self.notify()
def notify(self):
self._text.config(text=self._model._temp)
if __name__ == "__main__":
View().mainloop()