基于Python的数据传输对象(DTO)实现方法

数据传输对象(DTO)的主要目标是简化应用程序不同层之间的通信,尤其是在通过各种边界接口(如Web服务、REST API、消息代理或其他远程交互机制)传输数据时。在与其他系统交换信息时,关键是要最小化不必要的开销,例如冗余的序列化/反序列化,并确保清晰的数据结构,以表示发送者和接收者之间的特定合同。

在本文中,我打算探讨Python在实现DTO方面提供的能力。从内置工具开始,扩展到专门的库。在核心功能中,我希望强调类型和数据验证、对象创建以及转换为字典的能力。

基于Python类的DTO

让我们考虑一个基于Python类的DTO的示例。假设我们有一个用户模型,其中包括名字和姓氏:

class UserDTO:
   def __init__(self, **kwargs):
       self.first_name = kwargs.get("first_name")
       self.last_name = kwargs.get("last_name")
       self.validate_lastname()

   def validate_lastname(self):
       if len(self.last_name) <= 2:
           raise ValueError("姓氏长度必须大于2")

   def to_dict(self):
       return self.__dict__

   @classmethod
   def from_dict(cls, dict_obj):
       return cls(**dict_obj)

我们在DTO类中实现了用于创建类实例、将数据转换为字典以及添加验证逻辑的方法。现在,让我们看看如何使用它:

>>> user_dto = UserDTO.from_dict({'first_name': 'John', 'last_name': 'Doe'})
>>> user_dto.to_dict()
{'first_name': 'John', 'last_name': 'Doe'}

>>> user_dto = UserDTO.from_dict({'first_name': 'John', 'last_name': 'Do'})
ValueError: 姓氏长度必须大于2

这只是一个高度简化的示例。这种方法允许您实现各种功能。唯一的缺点是您需要手动定义所有内容,即使使用继承,也可能有大量的代码。

NamedTuple

创建Python中的DTO的另一种方法是使用NamedTuple

NamedTuple是Python标准库中的一个类(在Python 3.6中引入),表示具有命名属性的不可变元组。它是collections模块中namedtuple类的一种类型化且更易读的版本。

我们可以基于NamedTuple创建一个DTO,其中包含上述示例中用户的名字和姓氏:

from typing import NamedTuple

class UserDTO(NamedTuple):
    first_name: str
    last_name: str

现在,我们可以像这样创建UserDTO对象,并将对象转换为字典,以及通过字典创建对象:

>>> user_dto = UserDTO(**{'first_name': 'John', 'last_name': 'Doe'})
>>> user_dto.first_name
'John'
>>> user_dto
UserDTO(first_name='John', last_name='Doe')
>>> user_dto._asdict()
{'first_name': 'John', 'last_name': 'Doe'}
>>> user_dto.first_name = 'Bill'
AttributeError: 无法设置属性

它没有内置的类型和数据验证,但提供了更紧凑的定义和更易读的格式。它还是不可变的,在操作过程中提供了更多的安全性。只能传递定义的参数作为输入,并且有一个_asdict方法可将其转换为字典。

详细信息请参见这里

TypedDict

在Python中创建DTO对象的另一个选项是使用TypedDict,它从3.8版本开始引入。这种数据类型允许您创建带有固定键集和值类型注释的字典。

当您需要使用具有特定键集的字典时,TypedDict是一个很好的选择。

要创建对象,您需要从typing模块导入TypedDict数据类型。让我们为用户模型创建一个TypedDict

from typing import TypedDict

class UserDTO(TypedDict):
   first_name: str
   last_name: str

在这个示例中,我们定义了UserDTO类,它是TypedDict的子类。我们可以创建一个UserDTO对象并填充数据:

>>> user_dto = UserDTO(**{first_name: 'John', last_name: 'Doe'})
>>> user_dto
{first_name: 'John', last_name: 'Doe'}
>>> type(user_dto)
<class 'dict'>

我们可以使用它来定义具有固定键集和值类型注释的字典。这使得代码更易读,更可预测。此外,TypedDict提供了使用诸如keys()和values()等字典方法的能力,在某些情况下可能很有用。

详细信息请参见这里

Dataclass

Dataclass是一个装饰器,提供了一种简单的方式来创建用于存储数据的类。Dataclass使用类型注释来定义字段,然后生成创建和使用该类对象所需的所有必要方法。

要使用dataclass创建DTO,您需要添加dataclass装饰器并使用类型注释定义字段。例如,我们可以像这样使用dataclass创建用户模型的DTO:

from dataclasses import asdict, dataclass

@dataclass
class UserDTO:
   first_name: str
   last_name: str = ''

   def __post_init__(self):
       self.validate_lastname()

   def validate_lastname(self):
       if len(self.last_name) <= 2:
           raise ValueError("姓氏长度必须大于2")

现在,我们可以轻松地创建UserDTO对象,将它们转换为字典,并根据字典创建新对象:

>>> user_dto = UserDTO(**{'first_name': 'John', 'last_name': 'Doe'})
>>> user_dto
UserDTO(first_name='John', last_name='Doe')
>>> asdict(user_dto)
{'first_name': 'John', 'last_name': 'Doe'}
>>> user_dto = UserDTO(**{'first_name': 'John', 'last_name': 'Do'})
ValueError: 姓氏长度必须大于2

要创建不可变对象,您可以将参数frozen=True传递给装饰器。有一个asdict方法可用于转换为字典。此外,您可以实现验证方法并使用默认值。

总的来说,dataclasses比普通类更紧凑,比前面讨论的选项功能更强大。

详细信息请参见这里

Attr

创建DTO的另一种方法是使用attr模块。它与dataclass类似,但功能更强大,提供更紧凑的描述。该库可以使用命令pip install attrs进行安装。

import attr

@attr.s
class UserDTO:
   first_name: str = attr.ib(default="John", validator=attr.validators.instance_of(str))
   last_name: str = attr.ib(default="Doe", validator=attr.validators.instance_of(str))

在这里,我们使用装饰器定义了一个带有属性first_namelast_name的DTO类,同时设置了默认值和验证。

>>> user_dto = UserDTO(**{'first_name': 'John', 'last_name': 'Doe'})
>>> user_dto
UserDTO(first_name='John', last_name='Doe')
>>> user_dto = UserDTO()
>>> user_dto
UserDTO(first_name='John', last_name='Doe')
>>> user_dto = UserDTO(**{'first_name': 1, 'last_name': 'Doe'})
TypeError: ("'first_name' must be <class 'str'>...

通过这种方式,attrs模块提供了定义DTO类的强大而灵活的工具,如验证、默认值和转换。DTO对象也可以通过装饰器属性frozen=True变为不可变。还可以通过define装饰器进行初始化。

详细信息请参见这里

Pydantic

Pydantic库是Python中用于数据验证和数据转换的工具。它利用类型注释来定义数据模式,并将JSON数据转换为Python对象。

Pydantic用于方便地处理来自Web请求、配置文件、数据库和其他需要数据验证和转换的场景。

可以使用命令pip install pydantic来安装它。

from pydantic import BaseModel, Field, field_validator

class UserDTO(BaseModel):
   first_name: str
   last_name: str = Field(min_length=2, alias="lastName")
   age: int = Field(lt=100, description="年龄必须是正整数")

   @field_validator("age")
   def validate_age(cls, value):
       if value < 18:
           raise ValueError("年龄必须至少为18岁")
       return value

在这个示例中,我们定义了一个UserDTO模型,其中包含了字符串长度和最大年龄的基本验证。我们还指定了last_name属性的数据将通过参数lastName传递。

此外,作为示例,还演示了一个用于最小年龄的自定义验证器。

>>> user_dto = UserDTO(**{'first_name': 'John', 'lastName': 'Doe', 'age': 31})
>>> user_dto
UserDTO(first_name='John', last_name='Doe', age=31)

>>> user_dto.model_dump()
{'first_name': 'John', 'last_name': 'Doe', 'age': 31}

>>> user_dto.model_dump_json()
'{"first_name":"John","last_name":"Doe","age":31}'

>>> user_dto = UserDTO(**{'first_name': 'John', 'lastName': 'D', 'age': 3})
pydantic_core._pydantic_core.ValidationError: 2 validation errors for UserDTO
lastName
    String should have at least 2 characters [type=string_too_short, input_value='D', input_type=str]
age
    Value error, Age must be at least 18 [type=value_error, input_value=3, input_type=int]

Pydantic是一种多功能的工具。它是FastAPI中默认的数据模式和验证工具。它通过内置方法简化了对象到JSON格式的序列化和反序列化,提供了更易读的运行时提示。

详细信息请参见这里

总结

在本文中,我介绍了在Python中实现DTO的不同方法,从简单的方法到更复杂的方法。选择在项目中使用哪种方法取决于各种因素。

这包括您项目中的Python版本以及是否有能力安装新的依赖项。还取决于您是否计划使用验证、转换,或者是否仅需要简单的类型注释。

我希望这篇文章能帮助那些在寻找适用于其项目的Python中实现DTO的合适方法的人们。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值