1

我對某些Python類中的多重繼承設計有些疑問。此Python代碼中多重繼承的最佳實踐

問題是我想擴展ttk按鈕。這是我最初的建議(我省略了所有的源代碼在縮短的方法,除了初始化方法):

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 

但後來我意識到,我也希望這個ImgButton的一個子類,如下所示:

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 

繼承感覺自然是正確的。問題出現的時候,我也注意到ImgButton中的大多數方法都可以重用於我將來可能創建的任何Widget。

所以我想製作:

class MyWidget(ttk.Widget): 

爲把它與色彩幫助小部件,然後我需要ImgButton既從進myWidget和ttk.Button繼承所有方法:

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

or 

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

編輯:另外我希望我的目標是爲loggable,所以我做了這個類:

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__ 

所以現在:

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

or 

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

or 

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

# ... this could go on ... 

我來自Java和我不知道多重繼承的最佳做法。我不知道應該如何按照最佳順序排序父母,或者對設計這種多重繼承有用。

我已經搜索了關於該主題,發現了很多解釋MRO的資源,但沒有提到如何正確設計多重繼承。我不知道,即使我的設計是錯誤的,但我認爲這感覺很自然。

我將不勝感激的一些建議,以及對這個主題的一些鏈接或資源以及。

非常感謝。

回答

0

這幾天我一直在閱讀關於多重繼承的知識,並且我學到了很多東西。我最後將我的資源,資源和參考資料聯繫在一起。

我的主要和最詳細的資料來源是「Fluent python」,我發現它可以在線免費閱讀。

這說明了方法解析順序和設計景觀與多重繼承和做就OK了步驟:

  1. 標識和接口獨立的代碼。定義方法的類但不一定與實現(這些應該被覆蓋)。這些通常是ABC(抽象基類)。他們定義了創建「IS-A」關係的子類的類型

  2. 確定並分離mixin的代碼。 mixin是一個類,它應該帶來一系列相關的新方法實現在子中使用,但沒有定義適當的類型。根據這個定義,ABC可能是一個mixin,但不是相反的。該混入沒有定義,也不是一個接口,既不是類型

  3. 未來何時使用基本知識或類和混入繼承,你應該只從一個具體的超,和一些基本知識或混入繼承:

實施例:

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

在我的情況:

class ImgButton(ttk.Button, MyWidget): 
  • 如果類中的某些組合是特別有用的或頻繁的,則應該加入他們類定義下使用描述性名稱
  • 實施例:

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

    我認爲Loggable會是一個Mixin,因爲它收集了一個功能方便的實現,但沒有定義一個真實的類型。所以:

    class MyWidget(ttk.Widget, Loggable): # May be renamed to LoggableMixin 
    
  • 過度繼承

    有利於對象的組合物:如果可以通過把它保持在一個屬性,而不是延伸它或繼承想到使用一個類的任何方式從它,你應該避免繼承。

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

    Super is super

    Super is harmful

    Other problems with super

    Weird super behaviour

  • 1

    原則上,使用多重繼承增加了複雜性,所以除非我確定它的需要,否則我會避免它。從您的文章中,您已經瞭解了super()和MRO的用法。

    一個常見的建議是儘可能使用組合而不是多重繼承。

    另一個是僅從一個可實例化的父類中繼承,使用抽象類作爲其他父類。也就是說,他們將方法添加到這個子類中,但從來沒有自己實例化。就像使用Java中的接口一樣。那些抽象類也被稱爲mixin,但它們的使用(或濫用)也是有爭議的。見Mixins considered harmful

    至於你的tkinter代碼,除了記錄代碼縮進,我沒有看到問題。也許小部件可以有一個記錄器,而不是從它繼承。我認爲,與tkinter的危險是不必要的數以百計的可用方法之一的錯誤覆蓋。

    +0

    我已經想到用Tkinter的同樣的危險,但對做一個新的視覺控制感覺自然延伸現有的。我知道mixins,我沒有理由反對使用它們。我認爲現在我會使用繼承方法,並將父類Loggable和ttk.Widget創建爲MyWidget子類。 ImgButton將與父母MyWidget和ttk.Button一起成爲孩子。每個級別都有兩位父母,但進一步下去就不再需要多重繼承。 – madtyn