1

Ich habe einige Zweifel an dem Design der mehrfache Vererbung in einigen Python-Klassen.Best Practices für die Mehrfachvererbung in diesem Python-Code

Die Sache ist, dass ich die TTK-Taste erweitern wollte. Das war mein erster Vorschlag (ich Weglassen des gesamten Quelltext in Methoden zur Verkürzung, außer init Methoden):

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 

Aber ich später erkannte, dass ich auch eine Unterklasse dieser ImgButton wollte wie folgt:

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 

Die Erbschaft fühlt sich natürlich richtig an. Das Problem kam, als ich bemerkte, dass die meisten Methoden in ImgButton für jedes Widget wiederverwendbar sind, das ich in Zukunft erstellen kann.

So etwa ich denke, eine zu machen:

class MyWidget(ttk.Widget): 

denn in ihm alle Methoden setzen, die für Widgets mit Farbe helfen und dann brauche ich ImgButton sowohl von mywidget und ttk.Button zu erben:

class ImgButton(ttk.Button, MyWidget): ??? 

or 

class ImgButton(MyWidget, ttk.Button): ??? 

Edited: auch möchte ich meine Objekte speicherbare sein, so habe ich diese Klasse:

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 jetzt:

class ImgButton(Loggable, ttk.Button, MyWidget): ??? 

or 

class ImgButton(Loggable, MyWidget, ttk.Button): ??? 

or 

class ImgButton(MyWidget, Loggable, ttk.Button): ??? 

# ... this could go on ... 

ich von Java kommen und ich weiß nicht, Best Practices für die Mehrfachvererbung. Ich weiß nicht, wie ich die Eltern in der besten Reihenfolge sortieren sollte, oder etwas anderes, das nützlich für die Gestaltung dieser Mehrfachvererbung ist.

Ich habe nach dem Thema gesucht und eine Menge Ressourcen gefunden, die das MRO erklären, aber nichts darüber, wie man eine Mehrfachvererbung richtig gestaltet. Ich weiß nicht, ob sogar mein Design falsch gemacht wurde, aber ich dachte, dass es sich ziemlich natürlich anfühlt.

Ich wäre dankbar für einen Rat, und für einige Links oder Ressourcen zu diesem Thema auch.

Vielen Dank.

Antwort

0

Ich habe in diesen Tagen über Mehrfachvererbung gelesen und ich habe eine Menge Dinge gelernt. Ich habe meine Quellen, Ressourcen und Referenzen am Ende verlinkt.

Meine wichtigste und detaillierteste Quelle war das Buch "Fluent Python", das ich online zum kostenlosen Lesen fand.

Dies beschreibt die Methode Auflösung Ordnung und Design Szenerien mit Mehrfachvererbung und die Schritte, es zu tun ok:

  1. identifizieren und separater Code für Schnittstellen. Die Klassen, die Methoden definieren, aber nicht notwendigerweise mit Implementierungen (diese sollten überschrieben werden). Dies sind normalerweise ABCs (abstrakte Basisklasse). Sie definieren einen Typ für die Kindklasse, die eine "IS-A" -Beziehung erstellt.

  2. Identifizieren und trennen Sie den Code für Mixins. Ein Mixin ist eine Klasse, die ein Bündel verwandter neuer Methodenimplementierungen zur Verwendung im untergeordneten Element enthalten sollte, aber keinen geeigneten Typ definiert. Ein ABC könnte durch diese Definition ein Mix sein, aber nicht umgekehrt. Die mixin definiert nicht noch eine Schnittstelle, weder ein Typ

  3. kommend die ABCs oder Klassen zu verwenden und die Mixins vererben, Sie nur von einer konkreten Super und mehrere ABCs oder Mixins erben sollte:

Beispiel:

class MyClass(MySuperClass, MyABC, MyMixin1, MyMixin2): 

In meinem Fall:

class ImgButton(ttk.Button, MyWidget): 
  1. Wenn eine Kombination von Klassen besonders nützlich oder häufig ist, können Sie sie unter einer Klassendefinition mit einem beschreibenden Namen kommen sollten:

Beispiel:

class Widget(BaseWidget, Pack, Grid, Place): 
    pass 

Ich denke, Loggable wäre ein Mixin, weil es bequeme Implementierungen für eine Funktionalität sammelt, aber keinen echten Typ definiert. Also:

class MyWidget(ttk.Widget, Loggable): # May be renamed to LoggableMixin 
  1. Favor Objekt Komposition der Vererbung: Wenn Sie sich vorstellen können irgendeiner Weise eine Klasse der Verwendung von es in einem Attribut halten, anstatt sie zu erweitern oder vererben davon sollten Sie die Vererbung vermeiden.

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

    Super is super

    Super is harmful

    Other problems with super

    Weird super behaviour

1

Im Prinzip erhöht die Verwendung von Mehrfachvererbung die Komplexität, so dass ich es vermeiden würde, es sei denn, ich bin mir dessen sicher. In Ihrem Beitrag sehen Sie bereits die Verwendung von super() und dem MRO.

Eine allgemeine Empfehlung ist die Verwendung von Kompositionen anstelle von Mehrfachvererbung, wenn möglich.

Eine weitere Möglichkeit ist die Unterklasse von nur einer instanziierbaren Elternklasse, wobei abstrakte Klassen als die anderen Eltern verwendet werden. Das heißt, sie fügen Methoden zu dieser Unterklasse hinzu, werden aber niemals selbst instanziiert. Genauso wie die Verwendung von Schnittstellen in Java. Diese abstrakten Klassen werden auch Mixins genannt, aber ihre Verwendung (oder Missbrauch) ist ebenfalls umstritten. Siehe Mixins considered harmful.

Wie für Ihren Tkinter-Code, sehe ich neben dem Einrücken des Logger-Codes kein Problem. Vielleicht können Widgets einen Logger haben, anstatt ihn zu erben. Ich denke, mit tkinter besteht die Gefahr darin, dass versehentlich eine von Hunderten von verfügbaren Methoden außer Kraft gesetzt wird.

+0

ich schon dachte, die gleiche Gefahr mit tkinter, aber für Durch eine neue visuelle Kontrolle fühlt es sich natürlich an, eine bestehende zu erweitern. Ich kenne Mixins und ich habe keine Gründe gegen sie. Auswendig denke ich jetzt würde ich Vererbung verwenden und MyWidget Kind mit Eltern Loggable und ttk.Widget machen. ImgButton wäre Kind mit den Eltern MyWidget und ttk.Button. Zwei Eltern jeder Ebene, aber weiter unten würde es keine Notwendigkeit für Mehrfachvererbung mehr geben. – madtyn