Simple Python calculator applying MVC












3












$begingroup$


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()









share|improve this question











$endgroup$












  • $begingroup$
    Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers.
    $endgroup$
    – Mast
    Sep 17 '18 at 12:26
















3












$begingroup$


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()









share|improve this question











$endgroup$












  • $begingroup$
    Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers.
    $endgroup$
    – Mast
    Sep 17 '18 at 12:26














3












3








3





$begingroup$


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()









share|improve this question











$endgroup$




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()






python python-3.x mvc calculator tkinter






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Sep 17 '18 at 12:26









Mast

7,58263788




7,58263788










asked Sep 11 '18 at 12:34









saucecodesaucecode

967




967












  • $begingroup$
    Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers.
    $endgroup$
    – Mast
    Sep 17 '18 at 12:26


















  • $begingroup$
    Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers.
    $endgroup$
    – Mast
    Sep 17 '18 at 12:26
















$begingroup$
Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers.
$endgroup$
– Mast
Sep 17 '18 at 12:26




$begingroup$
Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers.
$endgroup$
– Mast
Sep 17 '18 at 12:26










2 Answers
2






active

oldest

votes


















4












$begingroup$

Don't import tkinter twice.



You're importing tkinter twice:



import tkinter as tk
from tkinter import *


Just import it once, and prefix everything with tk.:



import tkinter as tk
...
self._frame.pack(fill=tk.BOTH, ...)
...
self._text = tk.Label(..., justify=tk.RIGHT, anchor=tk.E, ...)


Group all of your layout code together



When you create a widget, then call grid, then create a widget, then call grid, etc, it makes it very hard to visualize the interface, and to see the logical and physical groupings. Instead, separate widget creation from widget layout.



This will also help expose some typos that you have (you try to save four different buttons as self._button_plus).



Example:



    self._text = tk.Label(self._frame, text="INIT", font=self._font, justify=RIGHT, anchor=E, bg="white", padx=20, pady=20)
self._button_0 = tk.Button(self._frame, text="0", font=self._font, command=lambda:self._controller.digit(0))
self._button_1 = tk.Button(self._frame, text="1", font=self._font, command=lambda:self._controller.digit(1))
self._button_2 = tk.Button(self._frame, text="2", font=self._font, command=lambda:self._controller.digit(2))
self._button_3 = tk.Button(self._frame, text="3", font=self._font, command=lambda:self._controller.digit(3))
self._button_4 = tk.Button(self._frame, text="4", font=self._font, command=lambda:self._controller.digit(4))
self._button_5 = tk.Button(self._frame, text="5", font=self._font, command=lambda:self._controller.digit(5))
self._button_6 = tk.Button(self._frame, text="6", font=self._font, command=lambda:self._controller.digit(6))
self._button_7 = tk.Button(self._frame, text="7", font=self._font, command=lambda:self._controller.digit(7))
self._button_8 = tk.Button(self._frame, text="8", font=self._font, command=lambda:self._controller.digit(8))
self._button_9 = tk.Button(self._frame, text="9", font=self._font, command=lambda:self._controller.digit(9))
self._button_plus = tk.Button(self._frame, text="+", font=self._font, command=lambda:self._controller.operator(Operator.PLUS))
self._button_minus = tk.Button(self._frame, text="-", font=self._font, command=lambda:self._controller.operator(Operator.MINUS))
self._button_mult = tk.Button(self._frame, text="*", font=self._font, command=lambda:self._controller.operator(Operator.MULT))
self._button_div = tk.Button(self._frame, text="/", font=self._font, command=lambda:self._controller.operator(Operator.DIV))
self._button_equals = tk.Button(self._frame, text="=", font=self._font, command=lambda:self._controller.operator(Operator.EQUALS))
self._button_clear = tk.Button(self._frame, text="C", font=self._font, command=self._model.reset)

self._text.grid(row="0", column="0", columnspan="4", sticky="NSWE")
self._button_0.grid(row="5", column="0", columnspan="2", sticky="NSWE")
self._button_1.grid(row="4", column="0", sticky="NSWE")
self._button_2.grid(row="4", column="1", sticky="NSWE")
self._button_3.grid(row="4", column="2", sticky="NSWE")
self._button_4.grid(row="3", column="0", sticky="NSWE")
self._button_5.grid(row="3", column="1", sticky="NSWE")
self._button_6.grid(row="3", column="2", sticky="NSWE")
self._button_7.grid(row="2", column="0", sticky="NSWE")
self._button_8.grid(row="2", column="1", sticky="NSWE")
self._button_9.grid(row="2", column="2", sticky="NSWE")
self._button_plus.grid(row="2", column="3", rowspan="2", sticky="NSWE")
self._button_minus.grid(row="1", column="3", sticky="NSWE")
self._button_mult.grid(row="1", column="2", sticky="NSWE")
self._button_div.grid(row="1", column="1", sticky="NSWE")
self._button_equals.grid(row="4", column="3", rowspan="2", sticky="NSWE")
self._button_clear.grid(row="1", column="0", sticky="NSWE")


Use a loop to create nearly identical widgets



Your buttons are all nearly identical, with the only difference being the character that they insert and the value they pass to the controller. You can cut down on the number of lines of code by using a loop, and storing the widgets in a dictionary:



self.buttons = {}
for char in ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"):
self.buttons[char] = tk.Button(self._frame, text=char, font=self._font, command=lambda c=char: self._controller.digit(c))


With that, instead of using something like self._button_1, you would use self.buttons['1'] if you need to reference the buttons elsewhere in the code. Plus, by using a loop you reinforce to the reader that these buttons are intended to be virtually identical.






share|improve this answer









$endgroup$





















    0












    $begingroup$

    Imports and layout have already been addressed by Bryan Oakley, so I will focus on the remaining two questions:




    • Is the MVC pattern OK this way?


    Your Controller does not serve any purpose right now. It just passes the value from the View class through to the Model.

    The Model on the other hand does all the work like checking what operator to apply and so on. But I think that's OK for now.

    What is definitely missing is a reference to View in the Controller class. The task of the Controller is to establish the connection between View and Model and to validate the data which is sent across these two.

    As you stated error handling is not part of your implementation yet, but division by zero would be a great example where your Controller definitely needs a reference to the View.

    In the current implementation your program would throw a ZeroDivisionError in Model.operator(). You could catch that there, but what next? You have no means to communicate this information back to the view.

    The Controller should be responsible to check for a possible division by zero. In your current implementation, Controller.operator() would be the correct place for that. In case of an error you might want to notify the View to display an error message and reset all data in your model.



    Furthermore, if your Controller holds a reference to the View, you could also get rid of the Observer pattern. Instead of letting the Model notify its View observer, the digit and operator methods can pass the value of _temp back to the View after calling the corresponding methods on the Model.






    • What's the best way to check passed parameters of methods? (see Model.digit(int))


    I guess that you want to make sure that the method actually received an integer. Your current implementation (taken from the Model class, although validating the inputs coming from the view should be in the responsibility of the Controller) will only detect if None is passed to it. A string, float or any other type will pass through undetected, potentially causing a TypeError in the second to last line:



    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()


    If you want to validate the type of a parameter beforehand you can da an isinstance check with a suitable superclass. In case of an integer, you would import numbers.Integral and check isinstance(digit, numbers.Integral). You could also directly check isinstance(digit, int), but that narrows down the possible data types to actual int, whereas numbers.Integral also allows for other compatible types which are registered as a virtual subclass of itself.

    The same goes for other types. If you want to allow for any iterable, you would check against collections.abc.Iterable instead of list or tuple or whatever.



    It is worth mentioning that in Python it is a widespread practice to not check the type at all and just put the critical code in a try ... except block and catching a possible TypeError. In your MVC example however this is not feasible since you want to validate the type already in the Controller before passing it to the Model which does the actual computation.






    share|improve this answer








    New contributor




    dudenr33 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
    Check out our Code of Conduct.






    $endgroup$














      Your Answer





      StackExchange.ifUsing("editor", function () {
      return StackExchange.using("mathjaxEditing", function () {
      StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
      StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
      });
      });
      }, "mathjax-editing");

      StackExchange.ifUsing("editor", function () {
      StackExchange.using("externalEditor", function () {
      StackExchange.using("snippets", function () {
      StackExchange.snippets.init();
      });
      });
      }, "code-snippets");

      StackExchange.ready(function() {
      var channelOptions = {
      tags: "".split(" "),
      id: "196"
      };
      initTagRenderer("".split(" "), "".split(" "), channelOptions);

      StackExchange.using("externalEditor", function() {
      // Have to fire editor after snippets, if snippets enabled
      if (StackExchange.settings.snippets.snippetsEnabled) {
      StackExchange.using("snippets", function() {
      createEditor();
      });
      }
      else {
      createEditor();
      }
      });

      function createEditor() {
      StackExchange.prepareEditor({
      heartbeatType: 'answer',
      autoActivateHeartbeat: false,
      convertImagesToLinks: false,
      noModals: true,
      showLowRepImageUploadWarning: true,
      reputationToPostImages: null,
      bindNavPrevention: true,
      postfix: "",
      imageUploader: {
      brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
      contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
      allowUrls: true
      },
      onDemand: true,
      discardSelector: ".discard-answer"
      ,immediatelyShowMarkdownHelp:true
      });


      }
      });














      draft saved

      draft discarded


















      StackExchange.ready(
      function () {
      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f203547%2fsimple-python-calculator-applying-mvc%23new-answer', 'question_page');
      }
      );

      Post as a guest















      Required, but never shown

























      2 Answers
      2






      active

      oldest

      votes








      2 Answers
      2






      active

      oldest

      votes









      active

      oldest

      votes






      active

      oldest

      votes









      4












      $begingroup$

      Don't import tkinter twice.



      You're importing tkinter twice:



      import tkinter as tk
      from tkinter import *


      Just import it once, and prefix everything with tk.:



      import tkinter as tk
      ...
      self._frame.pack(fill=tk.BOTH, ...)
      ...
      self._text = tk.Label(..., justify=tk.RIGHT, anchor=tk.E, ...)


      Group all of your layout code together



      When you create a widget, then call grid, then create a widget, then call grid, etc, it makes it very hard to visualize the interface, and to see the logical and physical groupings. Instead, separate widget creation from widget layout.



      This will also help expose some typos that you have (you try to save four different buttons as self._button_plus).



      Example:



          self._text = tk.Label(self._frame, text="INIT", font=self._font, justify=RIGHT, anchor=E, bg="white", padx=20, pady=20)
      self._button_0 = tk.Button(self._frame, text="0", font=self._font, command=lambda:self._controller.digit(0))
      self._button_1 = tk.Button(self._frame, text="1", font=self._font, command=lambda:self._controller.digit(1))
      self._button_2 = tk.Button(self._frame, text="2", font=self._font, command=lambda:self._controller.digit(2))
      self._button_3 = tk.Button(self._frame, text="3", font=self._font, command=lambda:self._controller.digit(3))
      self._button_4 = tk.Button(self._frame, text="4", font=self._font, command=lambda:self._controller.digit(4))
      self._button_5 = tk.Button(self._frame, text="5", font=self._font, command=lambda:self._controller.digit(5))
      self._button_6 = tk.Button(self._frame, text="6", font=self._font, command=lambda:self._controller.digit(6))
      self._button_7 = tk.Button(self._frame, text="7", font=self._font, command=lambda:self._controller.digit(7))
      self._button_8 = tk.Button(self._frame, text="8", font=self._font, command=lambda:self._controller.digit(8))
      self._button_9 = tk.Button(self._frame, text="9", font=self._font, command=lambda:self._controller.digit(9))
      self._button_plus = tk.Button(self._frame, text="+", font=self._font, command=lambda:self._controller.operator(Operator.PLUS))
      self._button_minus = tk.Button(self._frame, text="-", font=self._font, command=lambda:self._controller.operator(Operator.MINUS))
      self._button_mult = tk.Button(self._frame, text="*", font=self._font, command=lambda:self._controller.operator(Operator.MULT))
      self._button_div = tk.Button(self._frame, text="/", font=self._font, command=lambda:self._controller.operator(Operator.DIV))
      self._button_equals = tk.Button(self._frame, text="=", font=self._font, command=lambda:self._controller.operator(Operator.EQUALS))
      self._button_clear = tk.Button(self._frame, text="C", font=self._font, command=self._model.reset)

      self._text.grid(row="0", column="0", columnspan="4", sticky="NSWE")
      self._button_0.grid(row="5", column="0", columnspan="2", sticky="NSWE")
      self._button_1.grid(row="4", column="0", sticky="NSWE")
      self._button_2.grid(row="4", column="1", sticky="NSWE")
      self._button_3.grid(row="4", column="2", sticky="NSWE")
      self._button_4.grid(row="3", column="0", sticky="NSWE")
      self._button_5.grid(row="3", column="1", sticky="NSWE")
      self._button_6.grid(row="3", column="2", sticky="NSWE")
      self._button_7.grid(row="2", column="0", sticky="NSWE")
      self._button_8.grid(row="2", column="1", sticky="NSWE")
      self._button_9.grid(row="2", column="2", sticky="NSWE")
      self._button_plus.grid(row="2", column="3", rowspan="2", sticky="NSWE")
      self._button_minus.grid(row="1", column="3", sticky="NSWE")
      self._button_mult.grid(row="1", column="2", sticky="NSWE")
      self._button_div.grid(row="1", column="1", sticky="NSWE")
      self._button_equals.grid(row="4", column="3", rowspan="2", sticky="NSWE")
      self._button_clear.grid(row="1", column="0", sticky="NSWE")


      Use a loop to create nearly identical widgets



      Your buttons are all nearly identical, with the only difference being the character that they insert and the value they pass to the controller. You can cut down on the number of lines of code by using a loop, and storing the widgets in a dictionary:



      self.buttons = {}
      for char in ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"):
      self.buttons[char] = tk.Button(self._frame, text=char, font=self._font, command=lambda c=char: self._controller.digit(c))


      With that, instead of using something like self._button_1, you would use self.buttons['1'] if you need to reference the buttons elsewhere in the code. Plus, by using a loop you reinforce to the reader that these buttons are intended to be virtually identical.






      share|improve this answer









      $endgroup$


















        4












        $begingroup$

        Don't import tkinter twice.



        You're importing tkinter twice:



        import tkinter as tk
        from tkinter import *


        Just import it once, and prefix everything with tk.:



        import tkinter as tk
        ...
        self._frame.pack(fill=tk.BOTH, ...)
        ...
        self._text = tk.Label(..., justify=tk.RIGHT, anchor=tk.E, ...)


        Group all of your layout code together



        When you create a widget, then call grid, then create a widget, then call grid, etc, it makes it very hard to visualize the interface, and to see the logical and physical groupings. Instead, separate widget creation from widget layout.



        This will also help expose some typos that you have (you try to save four different buttons as self._button_plus).



        Example:



            self._text = tk.Label(self._frame, text="INIT", font=self._font, justify=RIGHT, anchor=E, bg="white", padx=20, pady=20)
        self._button_0 = tk.Button(self._frame, text="0", font=self._font, command=lambda:self._controller.digit(0))
        self._button_1 = tk.Button(self._frame, text="1", font=self._font, command=lambda:self._controller.digit(1))
        self._button_2 = tk.Button(self._frame, text="2", font=self._font, command=lambda:self._controller.digit(2))
        self._button_3 = tk.Button(self._frame, text="3", font=self._font, command=lambda:self._controller.digit(3))
        self._button_4 = tk.Button(self._frame, text="4", font=self._font, command=lambda:self._controller.digit(4))
        self._button_5 = tk.Button(self._frame, text="5", font=self._font, command=lambda:self._controller.digit(5))
        self._button_6 = tk.Button(self._frame, text="6", font=self._font, command=lambda:self._controller.digit(6))
        self._button_7 = tk.Button(self._frame, text="7", font=self._font, command=lambda:self._controller.digit(7))
        self._button_8 = tk.Button(self._frame, text="8", font=self._font, command=lambda:self._controller.digit(8))
        self._button_9 = tk.Button(self._frame, text="9", font=self._font, command=lambda:self._controller.digit(9))
        self._button_plus = tk.Button(self._frame, text="+", font=self._font, command=lambda:self._controller.operator(Operator.PLUS))
        self._button_minus = tk.Button(self._frame, text="-", font=self._font, command=lambda:self._controller.operator(Operator.MINUS))
        self._button_mult = tk.Button(self._frame, text="*", font=self._font, command=lambda:self._controller.operator(Operator.MULT))
        self._button_div = tk.Button(self._frame, text="/", font=self._font, command=lambda:self._controller.operator(Operator.DIV))
        self._button_equals = tk.Button(self._frame, text="=", font=self._font, command=lambda:self._controller.operator(Operator.EQUALS))
        self._button_clear = tk.Button(self._frame, text="C", font=self._font, command=self._model.reset)

        self._text.grid(row="0", column="0", columnspan="4", sticky="NSWE")
        self._button_0.grid(row="5", column="0", columnspan="2", sticky="NSWE")
        self._button_1.grid(row="4", column="0", sticky="NSWE")
        self._button_2.grid(row="4", column="1", sticky="NSWE")
        self._button_3.grid(row="4", column="2", sticky="NSWE")
        self._button_4.grid(row="3", column="0", sticky="NSWE")
        self._button_5.grid(row="3", column="1", sticky="NSWE")
        self._button_6.grid(row="3", column="2", sticky="NSWE")
        self._button_7.grid(row="2", column="0", sticky="NSWE")
        self._button_8.grid(row="2", column="1", sticky="NSWE")
        self._button_9.grid(row="2", column="2", sticky="NSWE")
        self._button_plus.grid(row="2", column="3", rowspan="2", sticky="NSWE")
        self._button_minus.grid(row="1", column="3", sticky="NSWE")
        self._button_mult.grid(row="1", column="2", sticky="NSWE")
        self._button_div.grid(row="1", column="1", sticky="NSWE")
        self._button_equals.grid(row="4", column="3", rowspan="2", sticky="NSWE")
        self._button_clear.grid(row="1", column="0", sticky="NSWE")


        Use a loop to create nearly identical widgets



        Your buttons are all nearly identical, with the only difference being the character that they insert and the value they pass to the controller. You can cut down on the number of lines of code by using a loop, and storing the widgets in a dictionary:



        self.buttons = {}
        for char in ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"):
        self.buttons[char] = tk.Button(self._frame, text=char, font=self._font, command=lambda c=char: self._controller.digit(c))


        With that, instead of using something like self._button_1, you would use self.buttons['1'] if you need to reference the buttons elsewhere in the code. Plus, by using a loop you reinforce to the reader that these buttons are intended to be virtually identical.






        share|improve this answer









        $endgroup$
















          4












          4








          4





          $begingroup$

          Don't import tkinter twice.



          You're importing tkinter twice:



          import tkinter as tk
          from tkinter import *


          Just import it once, and prefix everything with tk.:



          import tkinter as tk
          ...
          self._frame.pack(fill=tk.BOTH, ...)
          ...
          self._text = tk.Label(..., justify=tk.RIGHT, anchor=tk.E, ...)


          Group all of your layout code together



          When you create a widget, then call grid, then create a widget, then call grid, etc, it makes it very hard to visualize the interface, and to see the logical and physical groupings. Instead, separate widget creation from widget layout.



          This will also help expose some typos that you have (you try to save four different buttons as self._button_plus).



          Example:



              self._text = tk.Label(self._frame, text="INIT", font=self._font, justify=RIGHT, anchor=E, bg="white", padx=20, pady=20)
          self._button_0 = tk.Button(self._frame, text="0", font=self._font, command=lambda:self._controller.digit(0))
          self._button_1 = tk.Button(self._frame, text="1", font=self._font, command=lambda:self._controller.digit(1))
          self._button_2 = tk.Button(self._frame, text="2", font=self._font, command=lambda:self._controller.digit(2))
          self._button_3 = tk.Button(self._frame, text="3", font=self._font, command=lambda:self._controller.digit(3))
          self._button_4 = tk.Button(self._frame, text="4", font=self._font, command=lambda:self._controller.digit(4))
          self._button_5 = tk.Button(self._frame, text="5", font=self._font, command=lambda:self._controller.digit(5))
          self._button_6 = tk.Button(self._frame, text="6", font=self._font, command=lambda:self._controller.digit(6))
          self._button_7 = tk.Button(self._frame, text="7", font=self._font, command=lambda:self._controller.digit(7))
          self._button_8 = tk.Button(self._frame, text="8", font=self._font, command=lambda:self._controller.digit(8))
          self._button_9 = tk.Button(self._frame, text="9", font=self._font, command=lambda:self._controller.digit(9))
          self._button_plus = tk.Button(self._frame, text="+", font=self._font, command=lambda:self._controller.operator(Operator.PLUS))
          self._button_minus = tk.Button(self._frame, text="-", font=self._font, command=lambda:self._controller.operator(Operator.MINUS))
          self._button_mult = tk.Button(self._frame, text="*", font=self._font, command=lambda:self._controller.operator(Operator.MULT))
          self._button_div = tk.Button(self._frame, text="/", font=self._font, command=lambda:self._controller.operator(Operator.DIV))
          self._button_equals = tk.Button(self._frame, text="=", font=self._font, command=lambda:self._controller.operator(Operator.EQUALS))
          self._button_clear = tk.Button(self._frame, text="C", font=self._font, command=self._model.reset)

          self._text.grid(row="0", column="0", columnspan="4", sticky="NSWE")
          self._button_0.grid(row="5", column="0", columnspan="2", sticky="NSWE")
          self._button_1.grid(row="4", column="0", sticky="NSWE")
          self._button_2.grid(row="4", column="1", sticky="NSWE")
          self._button_3.grid(row="4", column="2", sticky="NSWE")
          self._button_4.grid(row="3", column="0", sticky="NSWE")
          self._button_5.grid(row="3", column="1", sticky="NSWE")
          self._button_6.grid(row="3", column="2", sticky="NSWE")
          self._button_7.grid(row="2", column="0", sticky="NSWE")
          self._button_8.grid(row="2", column="1", sticky="NSWE")
          self._button_9.grid(row="2", column="2", sticky="NSWE")
          self._button_plus.grid(row="2", column="3", rowspan="2", sticky="NSWE")
          self._button_minus.grid(row="1", column="3", sticky="NSWE")
          self._button_mult.grid(row="1", column="2", sticky="NSWE")
          self._button_div.grid(row="1", column="1", sticky="NSWE")
          self._button_equals.grid(row="4", column="3", rowspan="2", sticky="NSWE")
          self._button_clear.grid(row="1", column="0", sticky="NSWE")


          Use a loop to create nearly identical widgets



          Your buttons are all nearly identical, with the only difference being the character that they insert and the value they pass to the controller. You can cut down on the number of lines of code by using a loop, and storing the widgets in a dictionary:



          self.buttons = {}
          for char in ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"):
          self.buttons[char] = tk.Button(self._frame, text=char, font=self._font, command=lambda c=char: self._controller.digit(c))


          With that, instead of using something like self._button_1, you would use self.buttons['1'] if you need to reference the buttons elsewhere in the code. Plus, by using a loop you reinforce to the reader that these buttons are intended to be virtually identical.






          share|improve this answer









          $endgroup$



          Don't import tkinter twice.



          You're importing tkinter twice:



          import tkinter as tk
          from tkinter import *


          Just import it once, and prefix everything with tk.:



          import tkinter as tk
          ...
          self._frame.pack(fill=tk.BOTH, ...)
          ...
          self._text = tk.Label(..., justify=tk.RIGHT, anchor=tk.E, ...)


          Group all of your layout code together



          When you create a widget, then call grid, then create a widget, then call grid, etc, it makes it very hard to visualize the interface, and to see the logical and physical groupings. Instead, separate widget creation from widget layout.



          This will also help expose some typos that you have (you try to save four different buttons as self._button_plus).



          Example:



              self._text = tk.Label(self._frame, text="INIT", font=self._font, justify=RIGHT, anchor=E, bg="white", padx=20, pady=20)
          self._button_0 = tk.Button(self._frame, text="0", font=self._font, command=lambda:self._controller.digit(0))
          self._button_1 = tk.Button(self._frame, text="1", font=self._font, command=lambda:self._controller.digit(1))
          self._button_2 = tk.Button(self._frame, text="2", font=self._font, command=lambda:self._controller.digit(2))
          self._button_3 = tk.Button(self._frame, text="3", font=self._font, command=lambda:self._controller.digit(3))
          self._button_4 = tk.Button(self._frame, text="4", font=self._font, command=lambda:self._controller.digit(4))
          self._button_5 = tk.Button(self._frame, text="5", font=self._font, command=lambda:self._controller.digit(5))
          self._button_6 = tk.Button(self._frame, text="6", font=self._font, command=lambda:self._controller.digit(6))
          self._button_7 = tk.Button(self._frame, text="7", font=self._font, command=lambda:self._controller.digit(7))
          self._button_8 = tk.Button(self._frame, text="8", font=self._font, command=lambda:self._controller.digit(8))
          self._button_9 = tk.Button(self._frame, text="9", font=self._font, command=lambda:self._controller.digit(9))
          self._button_plus = tk.Button(self._frame, text="+", font=self._font, command=lambda:self._controller.operator(Operator.PLUS))
          self._button_minus = tk.Button(self._frame, text="-", font=self._font, command=lambda:self._controller.operator(Operator.MINUS))
          self._button_mult = tk.Button(self._frame, text="*", font=self._font, command=lambda:self._controller.operator(Operator.MULT))
          self._button_div = tk.Button(self._frame, text="/", font=self._font, command=lambda:self._controller.operator(Operator.DIV))
          self._button_equals = tk.Button(self._frame, text="=", font=self._font, command=lambda:self._controller.operator(Operator.EQUALS))
          self._button_clear = tk.Button(self._frame, text="C", font=self._font, command=self._model.reset)

          self._text.grid(row="0", column="0", columnspan="4", sticky="NSWE")
          self._button_0.grid(row="5", column="0", columnspan="2", sticky="NSWE")
          self._button_1.grid(row="4", column="0", sticky="NSWE")
          self._button_2.grid(row="4", column="1", sticky="NSWE")
          self._button_3.grid(row="4", column="2", sticky="NSWE")
          self._button_4.grid(row="3", column="0", sticky="NSWE")
          self._button_5.grid(row="3", column="1", sticky="NSWE")
          self._button_6.grid(row="3", column="2", sticky="NSWE")
          self._button_7.grid(row="2", column="0", sticky="NSWE")
          self._button_8.grid(row="2", column="1", sticky="NSWE")
          self._button_9.grid(row="2", column="2", sticky="NSWE")
          self._button_plus.grid(row="2", column="3", rowspan="2", sticky="NSWE")
          self._button_minus.grid(row="1", column="3", sticky="NSWE")
          self._button_mult.grid(row="1", column="2", sticky="NSWE")
          self._button_div.grid(row="1", column="1", sticky="NSWE")
          self._button_equals.grid(row="4", column="3", rowspan="2", sticky="NSWE")
          self._button_clear.grid(row="1", column="0", sticky="NSWE")


          Use a loop to create nearly identical widgets



          Your buttons are all nearly identical, with the only difference being the character that they insert and the value they pass to the controller. You can cut down on the number of lines of code by using a loop, and storing the widgets in a dictionary:



          self.buttons = {}
          for char in ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"):
          self.buttons[char] = tk.Button(self._frame, text=char, font=self._font, command=lambda c=char: self._controller.digit(c))


          With that, instead of using something like self._button_1, you would use self.buttons['1'] if you need to reference the buttons elsewhere in the code. Plus, by using a loop you reinforce to the reader that these buttons are intended to be virtually identical.







          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Sep 11 '18 at 15:43









          Bryan OakleyBryan Oakley

          1,641813




          1,641813

























              0












              $begingroup$

              Imports and layout have already been addressed by Bryan Oakley, so I will focus on the remaining two questions:




              • Is the MVC pattern OK this way?


              Your Controller does not serve any purpose right now. It just passes the value from the View class through to the Model.

              The Model on the other hand does all the work like checking what operator to apply and so on. But I think that's OK for now.

              What is definitely missing is a reference to View in the Controller class. The task of the Controller is to establish the connection between View and Model and to validate the data which is sent across these two.

              As you stated error handling is not part of your implementation yet, but division by zero would be a great example where your Controller definitely needs a reference to the View.

              In the current implementation your program would throw a ZeroDivisionError in Model.operator(). You could catch that there, but what next? You have no means to communicate this information back to the view.

              The Controller should be responsible to check for a possible division by zero. In your current implementation, Controller.operator() would be the correct place for that. In case of an error you might want to notify the View to display an error message and reset all data in your model.



              Furthermore, if your Controller holds a reference to the View, you could also get rid of the Observer pattern. Instead of letting the Model notify its View observer, the digit and operator methods can pass the value of _temp back to the View after calling the corresponding methods on the Model.






              • What's the best way to check passed parameters of methods? (see Model.digit(int))


              I guess that you want to make sure that the method actually received an integer. Your current implementation (taken from the Model class, although validating the inputs coming from the view should be in the responsibility of the Controller) will only detect if None is passed to it. A string, float or any other type will pass through undetected, potentially causing a TypeError in the second to last line:



              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()


              If you want to validate the type of a parameter beforehand you can da an isinstance check with a suitable superclass. In case of an integer, you would import numbers.Integral and check isinstance(digit, numbers.Integral). You could also directly check isinstance(digit, int), but that narrows down the possible data types to actual int, whereas numbers.Integral also allows for other compatible types which are registered as a virtual subclass of itself.

              The same goes for other types. If you want to allow for any iterable, you would check against collections.abc.Iterable instead of list or tuple or whatever.



              It is worth mentioning that in Python it is a widespread practice to not check the type at all and just put the critical code in a try ... except block and catching a possible TypeError. In your MVC example however this is not feasible since you want to validate the type already in the Controller before passing it to the Model which does the actual computation.






              share|improve this answer








              New contributor




              dudenr33 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
              Check out our Code of Conduct.






              $endgroup$


















                0












                $begingroup$

                Imports and layout have already been addressed by Bryan Oakley, so I will focus on the remaining two questions:




                • Is the MVC pattern OK this way?


                Your Controller does not serve any purpose right now. It just passes the value from the View class through to the Model.

                The Model on the other hand does all the work like checking what operator to apply and so on. But I think that's OK for now.

                What is definitely missing is a reference to View in the Controller class. The task of the Controller is to establish the connection between View and Model and to validate the data which is sent across these two.

                As you stated error handling is not part of your implementation yet, but division by zero would be a great example where your Controller definitely needs a reference to the View.

                In the current implementation your program would throw a ZeroDivisionError in Model.operator(). You could catch that there, but what next? You have no means to communicate this information back to the view.

                The Controller should be responsible to check for a possible division by zero. In your current implementation, Controller.operator() would be the correct place for that. In case of an error you might want to notify the View to display an error message and reset all data in your model.



                Furthermore, if your Controller holds a reference to the View, you could also get rid of the Observer pattern. Instead of letting the Model notify its View observer, the digit and operator methods can pass the value of _temp back to the View after calling the corresponding methods on the Model.






                • What's the best way to check passed parameters of methods? (see Model.digit(int))


                I guess that you want to make sure that the method actually received an integer. Your current implementation (taken from the Model class, although validating the inputs coming from the view should be in the responsibility of the Controller) will only detect if None is passed to it. A string, float or any other type will pass through undetected, potentially causing a TypeError in the second to last line:



                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()


                If you want to validate the type of a parameter beforehand you can da an isinstance check with a suitable superclass. In case of an integer, you would import numbers.Integral and check isinstance(digit, numbers.Integral). You could also directly check isinstance(digit, int), but that narrows down the possible data types to actual int, whereas numbers.Integral also allows for other compatible types which are registered as a virtual subclass of itself.

                The same goes for other types. If you want to allow for any iterable, you would check against collections.abc.Iterable instead of list or tuple or whatever.



                It is worth mentioning that in Python it is a widespread practice to not check the type at all and just put the critical code in a try ... except block and catching a possible TypeError. In your MVC example however this is not feasible since you want to validate the type already in the Controller before passing it to the Model which does the actual computation.






                share|improve this answer








                New contributor




                dudenr33 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                Check out our Code of Conduct.






                $endgroup$
















                  0












                  0








                  0





                  $begingroup$

                  Imports and layout have already been addressed by Bryan Oakley, so I will focus on the remaining two questions:




                  • Is the MVC pattern OK this way?


                  Your Controller does not serve any purpose right now. It just passes the value from the View class through to the Model.

                  The Model on the other hand does all the work like checking what operator to apply and so on. But I think that's OK for now.

                  What is definitely missing is a reference to View in the Controller class. The task of the Controller is to establish the connection between View and Model and to validate the data which is sent across these two.

                  As you stated error handling is not part of your implementation yet, but division by zero would be a great example where your Controller definitely needs a reference to the View.

                  In the current implementation your program would throw a ZeroDivisionError in Model.operator(). You could catch that there, but what next? You have no means to communicate this information back to the view.

                  The Controller should be responsible to check for a possible division by zero. In your current implementation, Controller.operator() would be the correct place for that. In case of an error you might want to notify the View to display an error message and reset all data in your model.



                  Furthermore, if your Controller holds a reference to the View, you could also get rid of the Observer pattern. Instead of letting the Model notify its View observer, the digit and operator methods can pass the value of _temp back to the View after calling the corresponding methods on the Model.






                  • What's the best way to check passed parameters of methods? (see Model.digit(int))


                  I guess that you want to make sure that the method actually received an integer. Your current implementation (taken from the Model class, although validating the inputs coming from the view should be in the responsibility of the Controller) will only detect if None is passed to it. A string, float or any other type will pass through undetected, potentially causing a TypeError in the second to last line:



                  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()


                  If you want to validate the type of a parameter beforehand you can da an isinstance check with a suitable superclass. In case of an integer, you would import numbers.Integral and check isinstance(digit, numbers.Integral). You could also directly check isinstance(digit, int), but that narrows down the possible data types to actual int, whereas numbers.Integral also allows for other compatible types which are registered as a virtual subclass of itself.

                  The same goes for other types. If you want to allow for any iterable, you would check against collections.abc.Iterable instead of list or tuple or whatever.



                  It is worth mentioning that in Python it is a widespread practice to not check the type at all and just put the critical code in a try ... except block and catching a possible TypeError. In your MVC example however this is not feasible since you want to validate the type already in the Controller before passing it to the Model which does the actual computation.






                  share|improve this answer








                  New contributor




                  dudenr33 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                  Check out our Code of Conduct.






                  $endgroup$



                  Imports and layout have already been addressed by Bryan Oakley, so I will focus on the remaining two questions:




                  • Is the MVC pattern OK this way?


                  Your Controller does not serve any purpose right now. It just passes the value from the View class through to the Model.

                  The Model on the other hand does all the work like checking what operator to apply and so on. But I think that's OK for now.

                  What is definitely missing is a reference to View in the Controller class. The task of the Controller is to establish the connection between View and Model and to validate the data which is sent across these two.

                  As you stated error handling is not part of your implementation yet, but division by zero would be a great example where your Controller definitely needs a reference to the View.

                  In the current implementation your program would throw a ZeroDivisionError in Model.operator(). You could catch that there, but what next? You have no means to communicate this information back to the view.

                  The Controller should be responsible to check for a possible division by zero. In your current implementation, Controller.operator() would be the correct place for that. In case of an error you might want to notify the View to display an error message and reset all data in your model.



                  Furthermore, if your Controller holds a reference to the View, you could also get rid of the Observer pattern. Instead of letting the Model notify its View observer, the digit and operator methods can pass the value of _temp back to the View after calling the corresponding methods on the Model.






                  • What's the best way to check passed parameters of methods? (see Model.digit(int))


                  I guess that you want to make sure that the method actually received an integer. Your current implementation (taken from the Model class, although validating the inputs coming from the view should be in the responsibility of the Controller) will only detect if None is passed to it. A string, float or any other type will pass through undetected, potentially causing a TypeError in the second to last line:



                  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()


                  If you want to validate the type of a parameter beforehand you can da an isinstance check with a suitable superclass. In case of an integer, you would import numbers.Integral and check isinstance(digit, numbers.Integral). You could also directly check isinstance(digit, int), but that narrows down the possible data types to actual int, whereas numbers.Integral also allows for other compatible types which are registered as a virtual subclass of itself.

                  The same goes for other types. If you want to allow for any iterable, you would check against collections.abc.Iterable instead of list or tuple or whatever.



                  It is worth mentioning that in Python it is a widespread practice to not check the type at all and just put the critical code in a try ... except block and catching a possible TypeError. In your MVC example however this is not feasible since you want to validate the type already in the Controller before passing it to the Model which does the actual computation.







                  share|improve this answer








                  New contributor




                  dudenr33 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                  Check out our Code of Conduct.









                  share|improve this answer



                  share|improve this answer






                  New contributor




                  dudenr33 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                  Check out our Code of Conduct.









                  answered 26 mins ago









                  dudenr33dudenr33

                  1293




                  1293




                  New contributor




                  dudenr33 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                  Check out our Code of Conduct.





                  New contributor





                  dudenr33 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                  Check out our Code of Conduct.






                  dudenr33 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                  Check out our Code of Conduct.






























                      draft saved

                      draft discarded




















































                      Thanks for contributing an answer to Code Review Stack Exchange!


                      • Please be sure to answer the question. Provide details and share your research!

                      But avoid



                      • Asking for help, clarification, or responding to other answers.

                      • Making statements based on opinion; back them up with references or personal experience.


                      Use MathJax to format equations. MathJax reference.


                      To learn more, see our tips on writing great answers.




                      draft saved


                      draft discarded














                      StackExchange.ready(
                      function () {
                      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f203547%2fsimple-python-calculator-applying-mvc%23new-answer', 'question_page');
                      }
                      );

                      Post as a guest















                      Required, but never shown





















































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown

































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown







                      Popular posts from this blog

                      Costa Masnaga

                      Create new schema in PostgreSQL using DBeaver

                      Fotorealismo