Rationale
As the code base grows over time, once in a while we may need to do some restructuring. When a function grows too big, we may consider breaking it down to a few smaller ones, each focusing on a single functionality. When a module grows too big, we may consider distributing its contents over a few modules, grouped by functionality. There is another type of subdivision of code that I never thought I would ever need to, but ended up having to do, which is to break down a single class definition into multiple files.
The need to break down a class emerges as I was writing a GUI application. The main frame in the main window contains several widgets, each with various degrees of complexity. To make all the widgets function and interact with each other, I ended up writing a really long class for the main frame, so long that it makes it increasingly difficult for me to navigate around the file to find what I’m after. So I searched for some methods to split a single class definition into several files, and the solution is to use mixins.
What are mixins?
According to the post by Leonardo Giordani, the concept of mixin originated from the lisp programming language. those are small classes that provide attributes but are not included in the standard inheritance tree, working more as "additions" to the current class than as proper ancestors.
I interpret mixins as a type of "plugin" class, just like you install a plugin to extend the functionality of a software rather than installing a different piece of software to fulfill the need, one uses mixin classes to extend the functionalities of a class.
Mixin classes are defined using the object class as its only parent, and contains no constructors. They may contain attributes or methods, but are preferably kept small.
For instance, the definition of the MainFrame
class in my GUI
application looks like this:
class MainFrame(QtWidgets.QWidget,_MainFrameLoadData.MainFrameLoadData, _MainFrameDataSlots.MainFrameDataSlots, _MainFrameToolBarSlots.MainFrameToolBarSlots, _MainFrameLibTreeSlots.MainFrameLibTreeSlots, _MainFrameFilterListSlots.MainFrameFilterListSlots, _MainFrameDocTableSlots.MainFrameDocTableSlots, _MainFrameMetaTabSlots.MainFrameMetaTabSlots, _MainFrameOtherSlots.MainFrameOtherSlots, _MainFrameProperties.MainFrameProperties ): def __init__(self,settings, parent): super(MainFrame,self).__init__() ... ...
Aside from QtWidgets.QWidget
which is the base class that
MainFrame
inherits from, all other classes in the inheritance list,
such as _MainFrameLoadData.MainFrameLoadData
and _MainFrameDataSlots.MainFrameDataSlots
, are all mixin
classes. They are all directly inherited from the object class and object
only, without a __init__
constructor in the definition.
For instance, the MainFrameProperties
mixin class defined in the file
_MainFrameProperties.py
stores some properties of the
MainFrame
. Its definition looks like this:
class MainFrameProperties: @property def _tabledata(self): if hasattr(self,'doc_table'): return self.doc_table.model().arraydata else: return None @property def _current_doc(self): if hasattr(self,'doc_table'): ... ... @property def _current_meta_dict(self): if hasattr(self,'t_meta'): if hasattr(self.t_meta,'fields_dict'): return self.t_meta.fields_dict return None ... ...
It is a collection of properties. (Note that in Python 3, class Name:
is the same as class Name(object):
. Some more info can be
found in this stackoverflow answer.)
The MainFrameToolBarSlots
mixin defined in the file
_MainFrameToolBarSlots.py
is responsible for providing the
functionalities of various buttons in the tool bar. Its definition looks
like this:
class MainFrameToolBarSlots: ####################################################################### # Tool bar button slots # ####################################################################### @pyqtSlot(QtWidgets.QAction) def addActionTriggered(self, action): """Add new document to current folder Args: action (QAction): QAction sent by menu button. This is a slot to the triggered signal of the Add button in the tool bar, and is responsible for handling adding new document to the current folder via PDF, bibtex, RIS or manual inputs. """ # action.data() is used in labelling the default add action. # if it's not None, return if action.data() is not None: ... ... @pyqtSlot(QtWidgets.QAction) def addFolderButtonClicked(self, action): """Add new folder Args: action (QAction): QAction sent by menu button. This is a slot to the triggered signal of the Add Folder button in the tool bar, and is responsible for handling adding new folder to the folder tree. """ item=self._current_folder_item ... ... def sortFolders(self): '''Sort folders in alphabetic order while keeping sys folder at top''' ... ...
All other mixins are defined in a similar manner: they focus on
providing some specific functionalities for a specific part of the
main frame, and each defined in a separate file. In this way, this
gigantic MainFrame
class is distributed over 10 different files, each kept at
a more manageable size, and the entire code base has a clearer structure.
Further reading
Here are some articles on mixins that you may be interested: