在 Python 中为类提供可选函数的最佳实践

Posted

技术标签:

【中文标题】在 Python 中为类提供可选函数的最佳实践【英文标题】:Best practice for providing optional functions for a class in Python 【发布时间】:2021-08-07 01:10:32 【问题描述】:

目前我正在编写一个带有插件系统的 Python 程序。要开发新插件,必须创建一个新类并从基本插件类继承。现在应该可以通过 mixins 添加可选功能。一些 mixin 提供了新功能,其他一些可以访问基类的内置类型,并可以对它们进行操作或更改它们。

下面是一个简化的结构:

import abc
import threading

class Base:
    def __init__(self):
        self.config = dict()

        if hasattr(self, "edit_config"):
            self.edit_config()

     def start(self):
        """Starts the Plugin"""
        if hasattr(self, "loop"):
            self._loop()

class AMixin:
    def edit_config(self):
        self.config["foo"] = 123

class BMixin(abc.ABC):
    def _loop(self):
        thread = threading.Thread(target=self.loop, daemon=True)
        thread.start()

    @abc.abstractmethod
    def loop(self):
        """Override this method with a while true loop to establish a ongoing loop
        """
        pass

class NewPlugin(Base, AMixin, BMixin):
    def loop(self):
        while True:
            print("Hello")

 plugin = NewPlugin()
 plugin.start()

解决这个问题的最佳方法是什么?

编辑:我需要让我的问题更具体。问题是上述是否是 Pythonic 方式,是否可以确保 mixin 与 Base 类一起专门继承。此外,在像 VSCode 这样的 IDE 中获得对例如的支持会很好。访问 Base 类的内置类型时自动完成,例如在 AMixin 中,当然不需要从它继承。

【问题讨论】:

【参考方案1】:

我会将对 mix-ins 提供的方法的调用移至这些类定义的 __init__ 方法。

import abc
import threading


class Base:
    def __init__(self, **kwargs):
        super.__init__(**kwargs)
        self.config = dict()


class AMixin:
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.edit_config()

    def edit_config(self):
        self.config["foo"] = 123


class BMixin(abc.ABC):
    def __init__(self, **kwargs):
        super().__init__(**kwargs):
        self.loop()

    def _loop(self):
        thread = threading.Thread(target=self.loop, daemon=True)
        thread.start()

    @abc.abstractmethod
    def loop(self):
        """Override this method with a while true loop to establish a ongoing loop
        """
        pass

class NewPlugin(Base, AMixin, BMixin):
    pass

当您实例化NewPluginBase.__init__AMixin.__init__BMixin.__init__ 的具体子类时,将按此顺序调用。

【讨论】:

感谢您的回答。基类的体系结构比简化示例中的要复杂一些。这意味着有必要在 __init__() 之外执行某些 mixin 函数。例如,仅在启动插件时。 根据您的编辑,这仍然有效。在BMixin.__init__ 中调用self.loop() 并不意味着将调用BMixin.loop。如果self 的类型为NewPlugin,则self.loop 解析为NewPlugin.loop,而不是BMixin.loop 我再次更改了问题和示例代码以澄清我的问题。关于您的回答:至于 BMixin,我想我理解您的解释。但是,如果我已经在 AMixin 的 __init__() 中运行 self.edit_confg(),self.config["foo"] 中的值已经设置为 123。如果由于架构的原因,应该首先更改值,例如自启动()?我是否正确解释了我的担忧? 对于BMixin,只需覆盖start 而不是__init__,使用super【参考方案2】:

如果要允许但不要求子类在基类调用的方法中定义某些行为,最简单的方法是在基类中声明该方法,有一个空实现,然后无条件调用该方法。这样你就不必在调用它之前检查方法是否存在。

class Base:
    def __init__(self):
        self.config = dict()
        self.edit_config()
    
    def start(self):
        self.loop()
    
    def edit_config(self):
        pass
    
    def loop(self):
        pass

class AMixin:
    def edit_config(self):
        self.config["foo"] = 123

class NewPlugin(AMixin, Base):
    def loop(self):
        for i in range(10):
            print("Hello")

请注意,您必须在超类列表中的Base 之前编写AMixin,以便其edit_config 方法覆盖Base 中的方法,而不是相反。您可以通过编写class AMixin(Base): 来避免这种情况,这样AMixin.edit_config 在方法解析顺序中始终会覆盖Base.edit_config

如果您想要求子类实现其中一种方法,那么您可以在基类的方法中使用raise TypeError() 而不是pass

【讨论】:

感谢您的回答和好建议。所以你可以说我基本上是在正确的轨道上。确定它是否是一种常见/pythonic 方式对我来说很重要。就我个人的感觉而言,我还是更愿意将可选函数只留在 mixins 中,并在基类中检查它们是否可用。 我建议在基类上使用一个空方法 - 另一个原因是您可能希望每个 mixin 的 edit_config 方法调用 super().edit_config() 以便多个 mixin 可以各自添加一些行为。 是的,没错。这绝对是我的想法。这样不同的方法调用同一个方法。你帮了我很多。谢谢

以上是关于在 Python 中为类提供可选函数的最佳实践的主要内容,如果未能解决你的问题,请参考以下文章

基础入门_Python-模块和包.运维开发中inspect自省模块的最佳实践?

Swift 风格:函数返回您需要的类型的可选类型以便继续,处理此问题的最佳实践是啥?

在objective-c中为原始类型分配内存的最佳实践

在 Elasticbeanstalk for Scala Apps 中为 Docker 多容器环境部署和托管工件的最佳实践是啥?

在python中为多个图包含唯一的最佳拟合线和r2值

登录python的最佳实践