I'm having some doubts with the design of mutiple inheritance in some Python classes.
The thing is that I wanted to extend the ttk button. This was my initial proposal (I'm omitting all the source code in methods for shortening, except init methods):
import tkinter as tk
import tkinter.ttk as ttk
class ImgButton(ttk.Button):
"""
This has all the behaviour for a button which has an image
"""
def __init__(self, master=None, **kw):
super().__init__(master, **kw)
self._img = kw.get('image')
def change_color(self, __=None):
"""
Changes the color of this widget randomly
:param __: the event, which is no needed
"""
pass
def get_style_name(self):
"""
Returns the specific style name applied for this widget
:return: the style name as a string
"""
pass
def set_background_color(self, color):
"""
Sets this widget's background color to that received as parameter
:param color: the color to be set
"""
pass
def get_background_color(self):
"""
Returns a string representing the background color of the widget
:return: the color of the widget
"""
pass
def change_highlight_style(self, __=None):
"""
Applies the highlight style for a color
:param __: the event, which is no needed
"""
pass
But I realized later that I wanted also a subclass of this ImgButton as follows:
import tkinter as tk
import tkinter.ttk as ttk
class MyButton(ImgButton):
"""
ImgButton with specifical purpose
"""
IMG_NAME = 'filename{}.jpg'
IMAGES_DIR = os.path.sep + os.path.sep.join(['home', 'user', 'myProjects', 'myProject', 'resources', 'images'])
UNKNOWN_IMG = os.path.sep.join([IMAGES_DIR, IMG_NAME.format(0)])
IMAGES = (lambda IMAGES_DIR=IMAGES_DIR, IMG_NAME=IMG_NAME: [os.path.sep.join([IMAGES_DIR, IMG_NAME.format(face)]) for face in [1,2,3,4,5] ])()
def change_image(self, __=None):
"""
Changes randomly the image in this MyButton
:param __: the event, which is no needed
"""
pass
def __init__(self, master=None, value=None, **kw):
# Default image when hidden or without value
current_img = PhotoImage(file=MyButton.UNKNOWN_IMG)
super().__init__(master, image=current_img, **kw)
if not value:
pass
elif not isinstance(value, (int, Die)):
pass
elif isinstance(value, MyValue):
self.myValue = value
elif isinstance(value, int):
self.myValue = MyValue(value)
else:
raise ValueError()
self.set_background_color('green')
self.bind('<Button-1>', self.change_image, add=True)
def select(self):
"""
Highlights this button as selected and changes its internal state
"""
pass
def toggleImage(self):
"""
Changes the image in this specific button for the next allowed for MyButton
"""
pass
The inheritance feels natural right to his point. The problem came when I noticed as well that most methods in ImgButton would be reusable for any Widget I may create in the future.
So I'm thinking about making a:
class MyWidget(ttk.Widget):
for putting in it all methods which help with color for widgets and then I need ImgButton to inherit both from MyWidget and ttk.Button:
class ImgButton(ttk.Button, MyWidget): ???
or
class ImgButton(MyWidget, ttk.Button): ???
Edited: Also I want my objects to be loggable, so I did this class:
class Loggable(object):
def __init__(self) -> None:
super().__init__()
self.__logger = None
self.__logger = self.get_logger()
self.debug = self.get_logger().debug
self.error = self.get_logger().error
self.critical = self.get_logger().critical
self.info = self.get_logger().info
self.warn = self.get_logger().warning
def get_logger(self):
if not self.__logger:
self.__logger = logging.getLogger(self.get_class())
return self.__logger
def get_class(self):
return self.__class__.__name__
So now:
class ImgButton(Loggable, ttk.Button, MyWidget): ???
or
class ImgButton(Loggable, MyWidget, ttk.Button): ???
or
class ImgButton(MyWidget, Loggable, ttk.Button): ???
# ... this could go on ...
I come from Java and I don't know best practices for multiple inheritance. I don't know how I should sort the parents in the best order or any other thing useful for designing this multiple inheritance.
I have searched about the topic and found a lot of resources explaining the MRO but nothing about how to correctly design a multiple inheritance. I don't know if even my design is wrongly made, but I thought it was feeling pretty natural.
I would be grateful for some advice, and for some links or resources on this topic as well.
Thank you very much.
I've been reading about multiple inheritance these days and I've learnt quite a lot of things. I have linked my sources, resources and references at the end.
My main and most detailed source has been the book "Fluent python", which I found available for free reading online.
This describes the method resolution order and design sceneries with multiple inheritance and the steps for doing it ok:
Identify and separate code for interfaces. The classes that define methods but not necessarily with implementations (these ones should be overriden). These are usually ABCs (Abstract Base Class). They define a type for the child class creating an "IS-A" relationship
Identify and separate code for mixins. A mixin is a class that should bring a bundle of related new method implementations to use in the child but does not define a proper type. An ABC could be a mixin by this definition, but not the reverse. The mixin doesn't define nor an interface, neither a type
When coming to use the ABCs or classes and the mixins inheriting, you should inherit from only one concrete superclass, and several ABCs or mixins:
Example:
class MyClass(MySuperClass, MyABC, MyMixin1, MyMixin2):
In my case:
class ImgButton(ttk.Button, MyWidget):
Example:
class Widget(BaseWidget, Pack, Grid, Place):
pass
I think Loggable would be a Mixin, because it gathers convenient implementations for a functionality, but does not define a real type. So:
class MyWidget(ttk.Widget, Loggable): # May be renamed to LoggableMixin
Favor object composition over inheritance: If you can think of any way of using a class by holding it in an attribute instead of extending it or inheriting from it, you should avoid inheritance.