Mastering GUI Architectures: Implementing MVC, MVP, and MVVM in Python
Overview
Software development often begins with a single, massive class that handles everything from database connections to button clicks. This "God class" approach makes code brittle and impossible to test. To build maintainable Graphical User Interfaces (GUIs), developers use architectural patterns to separate concerns. This tutorial explores the implementation of Model-View-Controller, Model-View-Presenter, and Model-View-ViewModel (MVVM) in Python. By decoupling the data (Model) from the visual interface (View), you create systems where changing the UI doesn't require rewriting your business logic.
Prerequisites
To follow this guide, you should possess a solid grasp of Python fundamentals, particularly Object-Oriented Programming (OOP) and class inheritance. Familiarity with basic GUI event loops and database operations using SQLite is beneficial. You will also need Python 3.8+ installed on your machine.
Key Libraries & Tools
- Tkinter: The built-in Python library for creating standard desktop GUIs.
- PyQt: A set of Python bindings for the Qt framework, ideal for professional-grade applications and Model-View-ViewModel implementations.
- SQLite: A lightweight, disk-based database for persisting application data.
- Typing & Protocols: Python's
typingmodule, specificallyProtocol, for defining structural subtyping and achieving loose coupling.
Code Walkthrough: Moving from MVC to MVP
In a standard Model-View-Controller setup, the View often retains a direct reference to the Model to fetch data. This creates a triangular dependency that complicates testing. The Model-View-Presenter pattern breaks this by turning the Presenter into a strict intermediary.
1. Defining Abstract Interfaces with Protocols
We use Protocols to ensure the Presenter and View communicate through abstractions rather than concrete classes.

from typing import Protocol, List
class View(Protocol):
def clear_entry(self) -> None: ...
def get_entry_text(self) -> str: ...
def update_task_list(self, tasks: List[str]) -> None: ...
def create_ui(self, presenter: "Presenter") -> None: ...
2. The Presenter Implementation
The Presenter handles the logic of what happens when a user interacts with the UI. It never touches the UI widgets directly; it only calls methods defined in the View protocol.
class ToDoPresenter:
def __init__(self, model, view: View):
self.model = model
self.view = view
def handle_add_task(self, event=None):
text = self.view.get_entry_text()
if text:
self.model.add_task(text)
self.view.clear_entry()
self.update_task_list()
def update_task_list(self):
tasks = self.model.get_tasks()
self.view.update_task_list(tasks)
3. The View Implementation
In Model-View-Presenter, the View is "passive." It notifies the Presenter of user actions and waits for the Presenter to tell it what to display.
class ToDoListView(tk.Tk):
def update_task_list(self, tasks: List[str]):
self.listbox.delete(0, tk.END)
for task in tasks:
self.listbox.insert(tk.END, task)
Syntax Notes
- Protocols vs. ABCs: While Abstract Base Classes (ABCs) require explicit inheritance, Protocols allow for structural subtyping. This means any class with the required methods satisfies the interface without being a formal subclass.
- Forward References: Using
"Presenter"as a string type hint allows you to reference a class that hasn't been fully defined yet, avoiding circular import issues.
Practical Examples
- Mobile Apps: Model-View-ViewModel is the standard for modern Android and iOS development because of its powerful data-binding capabilities.
- Web Frameworks: Django uses a variation called Model-Template-View (Model-Template-View), where the framework itself acts as the Controller.
- Desktop Tools: Use Model-View-Presenter when you need highly testable desktop software where the UI logic is separated from the widget library.
Tips & Gotchas
- The Dependency Triangle: In Model-View-Controller, if your View knows about the Model and the Controller knows about both, you’ve created a mess. Transition to Model-View-Presenter to force the View and Model to be oblivious of each other.
- Data Binding Overhead: Model-View-ViewModel is powerful but requires a framework that supports data binding (like PyQt). Don't try to manual-code a binder in Tkinter unless you want a headache.
- Boilerplate Warning: Separating your code into these patterns increases the number of files and lines of code. It feels like overkill for small scripts, but it’s a lifesaver as your project grows.
- Model-View-Presenter
- 22%· software architecture
- Model-View-ViewModel
- 17%· software architecture
- Model-View-Controller
- 13%· software architecture
- Python
- 13%· programming languages
- PyQt
- 9%· libraries
- Other topics
- 26%

Which Software Architecture Should You Use: MVC, MVP, or MVVM?
WatchArjanCodes // 24:27
On this channel, I post videos about programming and software design to help you take your coding skills to the next level. I'm an entrepreneur and a university lecturer in computer science, with more than 20 years of experience in software development and design. If you're a software developer and you want to improve your development skills, and learn more about programming in general, make sure to subscribe for helpful videos. I post a video here every Friday. If you have any suggestion for a topic you'd like me to cover, just leave a comment on any of my videos and I'll take it under consideration. Thanks for watching!