此 Python 代码中多重继承的最佳实践
Best practices for multiple inheritance in this Python code
我对某些 Python class 中的多重继承设计有一些疑问。
问题是我想扩展 ttk 按钮。这是我最初的建议(我省略了缩短方法中的所有源代码,init 方法除外):
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 的子class,如下所示:
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): ???
已编辑: 另外我希望我的 objects 可以记录,所以我这样做了 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__
所以现在:
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,我不知道多重继承的最佳实践。我不知道我应该如何以最佳顺序对 parents 或任何其他对设计此多重继承有用的东西进行排序。
我搜索了有关该主题的内容,发现了很多解释 MRO 的资源,但没有关于如何正确设计多重继承的内容。不知道是不是我设计的有问题,总之感觉很自然。
如果能提供一些建议,以及有关此主题的一些链接或资源,我将不胜感激。
非常感谢。
原则上,使用多重继承会增加复杂性,所以除非我确定它的需要,否则我会避免它。从您的 post 来看,您已经知道 super() 和 MRO 的使用。
一个常见的建议是尽可能使用组合而不是多重继承。
另一种是仅从一个可实例化的父对象 class 继承 class,使用抽象 classes 作为其他父对象。也就是说,他们向这个 subclass 添加方法,但永远不会自己实例化。就像Java中接口的使用一样。那些抽象的 classes 也被称为 mixins,但它们的使用(或滥用)也是有争议的。参见 Mixins considered harmful。
至于您的 tkinter 代码,除了记录器代码缩进外,我没有发现任何问题。也许小部件可以有一个记录器而不是从它继承。我认为 tkinter 的危险在于错误地覆盖了数百种可用方法中的一种。
这些天我一直在阅读有关多重继承的内容,并且学到了很多东西。我在最后链接了我的来源、资源和参考资料。
我的主要和最详细的来源是书 "Fluent python",我发现它可以免费在线阅读。
这里介绍了多重继承的方法解析顺序和设计场景以及实现步骤ok:
识别并分离接口代码。 classes 定义了方法但不一定有实现(这些应该被覆盖)。这些通常是 ABC(抽象基础 Class)。他们为 child class 定义类型,创建 "IS-A" 关系
识别并分离 mixin 的代码。 mixin 是一个 class,它应该带来一堆相关的新方法实现以在 child 中使用,但没有定义适当的类型。根据这个定义,ABC 可以是 mixin,但反之则不行。 mixin 既没有定义接口也没有定义类型
当开始使用 ABC 或 classes 和 mixins 继承时,你应该只继承一个具体的超级class, 和几个 ABCs 或 mixins:
示例:
class MyClass(MySuperClass, MyABC, MyMixin1, MyMixin2):
就我而言:
class ImgButton(ttk.Button, MyWidget):
- 如果 class 的某些组合特别有用或经常使用,您应该在 class 定义下使用描述性名称 加入它们:
示例:
class Widget(BaseWidget, Pack, Grid, Place):
pass
我认为 Loggable 将是一个 Mixin,因为它收集了功能的方便实现,但没有定义真正的类型。所以:
class MyWidget(ttk.Widget, Loggable): # May be renamed to LoggableMixin
object 组合优于继承:如果你能想出任何使用 class 的方法属性而不是扩展它或继承它,你应该避免继承。
"Fluent python" - (Chapter 12) in Google books
我对某些 Python class 中的多重继承设计有一些疑问。
问题是我想扩展 ttk 按钮。这是我最初的建议(我省略了缩短方法中的所有源代码,init 方法除外):
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 的子class,如下所示:
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): ???
已编辑: 另外我希望我的 objects 可以记录,所以我这样做了 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__
所以现在:
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,我不知道多重继承的最佳实践。我不知道我应该如何以最佳顺序对 parents 或任何其他对设计此多重继承有用的东西进行排序。
我搜索了有关该主题的内容,发现了很多解释 MRO 的资源,但没有关于如何正确设计多重继承的内容。不知道是不是我设计的有问题,总之感觉很自然。
如果能提供一些建议,以及有关此主题的一些链接或资源,我将不胜感激。
非常感谢。
原则上,使用多重继承会增加复杂性,所以除非我确定它的需要,否则我会避免它。从您的 post 来看,您已经知道 super() 和 MRO 的使用。
一个常见的建议是尽可能使用组合而不是多重继承。
另一种是仅从一个可实例化的父对象 class 继承 class,使用抽象 classes 作为其他父对象。也就是说,他们向这个 subclass 添加方法,但永远不会自己实例化。就像Java中接口的使用一样。那些抽象的 classes 也被称为 mixins,但它们的使用(或滥用)也是有争议的。参见 Mixins considered harmful。
至于您的 tkinter 代码,除了记录器代码缩进外,我没有发现任何问题。也许小部件可以有一个记录器而不是从它继承。我认为 tkinter 的危险在于错误地覆盖了数百种可用方法中的一种。
这些天我一直在阅读有关多重继承的内容,并且学到了很多东西。我在最后链接了我的来源、资源和参考资料。
我的主要和最详细的来源是书 "Fluent python",我发现它可以免费在线阅读。
这里介绍了多重继承的方法解析顺序和设计场景以及实现步骤ok:
识别并分离接口代码。 classes 定义了方法但不一定有实现(这些应该被覆盖)。这些通常是 ABC(抽象基础 Class)。他们为 child class 定义类型,创建 "IS-A" 关系
识别并分离 mixin 的代码。 mixin 是一个 class,它应该带来一堆相关的新方法实现以在 child 中使用,但没有定义适当的类型。根据这个定义,ABC 可以是 mixin,但反之则不行。 mixin 既没有定义接口也没有定义类型
当开始使用 ABC 或 classes 和 mixins 继承时,你应该只继承一个具体的超级class, 和几个 ABCs 或 mixins:
示例:
class MyClass(MySuperClass, MyABC, MyMixin1, MyMixin2):
就我而言:
class ImgButton(ttk.Button, MyWidget):
- 如果 class 的某些组合特别有用或经常使用,您应该在 class 定义下使用描述性名称 加入它们:
示例:
class Widget(BaseWidget, Pack, Grid, Place):
pass
我认为 Loggable 将是一个 Mixin,因为它收集了功能的方便实现,但没有定义真正的类型。所以:
class MyWidget(ttk.Widget, Loggable): # May be renamed to LoggableMixin
object 组合优于继承:如果你能想出任何使用 class 的方法属性而不是扩展它或继承它,你应该避免继承。
"Fluent python" - (Chapter 12) in Google books