2

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.

madtyn
  • 1,469
  • 27
  • 55

2 Answers2

5

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:

  1. 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

  2. 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

  3. 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):
  1. If some combination of classes is particularly useful or frequent, you should join them under a class definition with a descriptive name:

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
  1. 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.

    "Fluent python" - (Chapter 12) in Google books

    Super is super

    Super is harmful

    Other problems with super

    Weird super behaviour

madtyn
  • 1,469
  • 27
  • 55
1

In principle, use of multiple inheritance increases complexity, so unless I am certain of its need, I would avoid it. From your post you already look aware of the use of super() and the MRO.

A common recommendation is to use composition instead of multiple inheritance, when possible.

Another one is to subclass from only one instantiable parent class, using abstract classes as the other parents. That is, they add methods to this subclass, but never get instantiated themselves. Just like the use of interfaces in Java. Those abstract classes are also called mixins, but their use (or abuse) is also debatable. See Mixins considered harmful.

As for your tkinter code, besides logger code indentation, I don't see a problem. Maybe widgets can have a logger instead of inheriting from it. I think with tkinter the danger is the unwanted override by mistake of one of the hundreds of available methods.

progmatico
  • 4,714
  • 1
  • 16
  • 27
  • I already thought of the same danger with tkinter, but for making a new visual control it feels natural to extend an existing one. I know mixins and I have no reasons against using them. By heart, I think now I would use inheritance and make MyWidget child with parents Loggable and ttk.Widget. ImgButton would be child with parents MyWidget and ttk.Button. Two parents each level, but further down there would be no more need for multiple inheritance. – madtyn Oct 15 '17 at 02:09