在 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
当您实例化NewPlugin
、Base.__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 风格:函数返回您需要的类型的可选类型以便继续,处理此问题的最佳实践是啥?
在 Elasticbeanstalk for Scala Apps 中为 Docker 多容器环境部署和托管工件的最佳实践是啥?