python - 具有多种风格的对象的 OOP 方法
问题描述
我一直在编写代码来解析和提取机器人发送的消息中的信息。只有几种不同类型的消息,但每一种都包含我感兴趣的完全不同类型的信息,我正在努力寻找在我的代码中将它们作为对象处理的最佳方法。
如果我使用 Haskell,我只需创建一个类型Message
并为每种消息定义一个定制的构造函数
data Message = Greeting Foo Bar | Warning Yadda Yadda Yadda | ...
这是一种非常好的和干净的方式,既可以将它们全部放在同一type
位置,又可以轻松区分消息种类。
如何以一种 OOP 友好(或更好的 Pythonic)方式设计对象类以达到这种效果?我想到了两种方法,即:
Message
为每种消息定义一个基类并对其进行子类化。优点:概念上干净。缺点:大量样板代码,并没有真正使代码非常可读或不同消息类之间的关系清晰。定义一个通用类
Message
,它代表每种消息类型。它将有一个属性.type
来区分消息类型,并且它的__init__
函数将相应地实例化适合于消息类型的属性。优点:代码简单,实用。缺点:让类的属性如此不可预测似乎是一种不好的做法,而且通常感觉不对。
但我对两者都不完全满意。虽然我意识到这只是一个小程序,但我想我正在利用它作为学习更多关于使用抽象和软件架构的机会。有人能给我指路吗?
解决方案
对于消息类设计,我会使用数据类来最小化样板。您可以完全专注于以下领域:
from dataclasses import dataclass
class Message:
# common message methods
@dataclass
class Greeting(Message):
foo: str
bar: int
@dataclass
class Warning(Message):
yadda: list[str]
一个简单的项目通常不需要更多。您可以将@classmethod
工厂添加到Message
基类以帮助生成特定的消息类型,并且如果在不同类型之间共享公共属性,Message
也可以是工厂本身。@dataclass
也就是说,一旦您开始考虑序列化和反序列化要求,使用枚举type
字段可能会有所帮助。
为了说明这一点:对于包含自动化 OpenAPI 3.1 文档的当前 RESTFul API 项目,我们使用Marshmallow处理与 JSON 之间的转换,使用marshmallow-dataclasses以避免重复定义模式和验证,以及marshmallow-oneofschema反映类型不同的类层次结构的多态模式,就像您的Message
示例一样。
使用 3rd-party 库会限制您的选择,因此我使用元编程(主要是类型注释)来简洁地定义这种以枚举为键的多态类型层次结构class.__init_subclass__
。Generic
您的消息类型将表示如下:
class MessageType(enum.Enum):
greeting = "greeting"
warning = "warning"
# ...
@dataclass
class _BaseMessage(PolymorphicType[MessageType]):
type: MessageType
# ...
@dataclass
class Greeting(_BaseMessage, type_key=MessageType.greeting):
foo: str
bar: int
@dataclass
class Warning(_BaseMessage, type_key=MessageType.warning):
yadda: list[str]
MessageSchema = _BaseMessage.OneOfSchema("MessageSchema")
之后使用 JSON 从 JSON 加载消息,根据字典中MessageSchema.load()
的键生成特定实例,例如"type"
message = MessageSchema.load({"type": "greeting", "foo": "spam", "bar": 42})
isinstance(message, Greeting) # True
MessageSchema.dump()
无论输入类型如何,都会为您提供合适的 JSON 输出:
message = Warning([42, 117])
MessageSchema.dump(message) # {"type": "warning", "yadda": [42, 117]}
正是在enum
这里的使用使集成工作得最好;PolymorphicType
是处理大部分繁重工作以使_BaseMessage.OneOfSchema()
调用在最后工作的自定义类。您不必使用元编程来实现最后一部分,但对我们来说,它减少了大部分marshmallow-oneschema
样板文件。
此外,我们还获得了反映每种特定消息类型的 OpenAPI 模式,Redocly 等文档工具知道如何处理:
components:
schemas:
Message:
oneOf:
- $ref: '#/components/schemas/Greeting'
- $ref: '#/components/schemas/Warning'
discriminator:
propertyName: type
mapping:
greeting: '#/components/schemas/Greeting'
warning: '#/components/schemas/Warning'
Greeting:
type: object
properties:
type:
type: string
default: greeting
foo:
type: string
bar:
type: integer
Warning:
type: object
properties:
type:
type: string
default: warning
yadda:
type: array
items:
type: string
推荐阅读
- javascript - 为什么长度为 3 的字符串的字节长度为 3?
- reactjs - 下拉按钮反应将 json ID 传递到 HREF
- java - Spring数据JPA保存全部或全部以及异常回滚
- python - 特金特。滚动条在画布上不起作用
- javascript - 禁用 Web 应用程序的 IOS Safari 中的捏缩放?
- python - Python中的table()等效函数(来自R)
- python-3.x - 使用 Python 模块 aiohttp 捕获 http 错误连接的正确方法是什么?
- javascript - 使用 firebase auth 方法检查错误的正确方法是什么?
- python-3.x - 使用 pandas cut 将三组分成两组
- python - 训练 mlp , val_accuracy 始终等于 0