Dataclasses vs Attrs vs Pydantic
分类:数据科学 • 阅读时间:6分钟
Python 3.7引入了dataclasses,这是一个方便的装饰器,可以使创建类变得更加容易和无缝。本文将比较普通类、”dataclass”和使用attrs的类。Dataclasses基于attrs开发,attrs是一个Python包,同样旨在使创建类变得更加愉快。Dataclasses包含在标准库中(需要Python 3.7+),而要使用attrs,必须通过pip安装(例如,pip install attrs)。它们主要用于自动化编写魔术方法(有时)痛苦的体验。您可以在之前的文章中阅读更多关于魔术方法的内容:https://jackmckew.dev/dunders-in-python.html
Dataclasses vs Attrs vs Pydantic: 功能对比
| 功能 |
Dataclass |
Attrs |
Pydantic |
| 冻结实例 |
✅ |
✅ |
✅ |
| 默认值 |
✅ |
✅ |
✅ |
| 转元组 |
✅ |
✅ |
✅ |
| 转字典 |
✅ |
✅ |
✅ |
| 验证器 |
❌ |
✅ |
✅ |
| 转换器 |
❌ |
✅ |
✅ |
| 槽类优化 |
❌ |
✅ |
❌ |
| 编程式创建 |
❌ |
✅ |
❌ |
何时使用Dataclasses
Dataclasses主要用于将变量”分组”在一起。在以下情况下选择dataclasses:
- 主要关注的是变量的类型,而不是值
- 添加另一个包依赖不是小事
何时使用Attrs
Attrs既涉及分组又涉及验证。在以下情况下选择attrs:
- 您关心性能(attrs支持槽类生成,针对CPython进行了优化)
何时使用Pydantic
Pydantic是关于彻底的数据验证。在以下情况下选择pydantic:
示例类
首先,让我们从Python中默认实现的示例类开始。
我们将在类定义中使用类型提示,这是确保变量是我们预期类型的最佳实践。类型提示也集成到attrs中用于创建类。
1 2 3 4 5 6 7
| import typing
class Data: def __init__(self, x: float=None, y:float=None, kwargs:typing.Dict=None): self.x = x self.y = y self.kwargs = kwargs
|
传递给__init__构造函数的参数在实例化类的参数时会重复。如果参数和参数不匹配,通常不会出现这种情况。幸运的是,dataclasses和attrs都可以帮助我们解决这个问题(我们将在后面看到)。
现在为了演示dataclasses和attrs为我们自动化的所有不同功能,让我们定义一个函数,该函数接受类构造函数并为我们每个类打印出所有不同的元素。
1 2 3 4 5 6 7 8 9
| def class_tester(class_constructor): test_class_1 = class_constructor() test_class_2 = class_constructor()
print(f"Repr/str魔术方法表示: {test_class_1}") print(f"相等性魔术方法(使用==)(如果实现应为True): {test_class_1 == test_class_2}") print(f"相等性魔术方法(使用is)(如果实现应为True): {test_class_1 is test_class_2}")
class_tester(Data)
|
输出:
1 2 3
| Repr/str魔术方法表示: <__main__.Data object at 0x00000269A5758A90> 相等性魔术方法(使用==)(如果实现应为True): False 相等性魔术方法(使用is)(如果实现应为True): False
|
Dataclasses
Dataclasses默认自动为类初始化一堆魔术方法,例如:
__init__ 类的初始化方法
__repr__ 类在使用print()时的表示方式
__str__ 类作为字符串的表示方式(与__repr__一起调用)
__eq__ 使用相等运算符时使用(例如,==)
__hash__ 类的哈希值(与__eq__一起调用)
还有其他一堆也可以自动化的魔术方法,详情见:https://docs.python.org/3/library/dataclasses.html
感谢Twitter上的Michael Kosher:值得注意的是,可以使用__post_init__钩子向dataclasses添加验证。但是,相对于attrs/#pydantic来说,它相当低级。我做了类似的比较:https://mpkocher.github.io/2019/05/22/Dataclasses-in-Python-3-7/
1 2 3 4 5 6 7 8 9
| from dataclasses import dataclass
@dataclass class Data: x: float = None y: float = None kwargs: typing.Dict = None
class_tester(Data)
|
输出:
1 2 3
| Repr/str魔术方法表示: Data(x=None, y=None, kwargs=None) 相等性魔术方法(使用==)(如果实现应为True): True 相等性魔术方法(使用is)(如果实现应为True): False
|
Attrs
最后我们有了attrs类,attrs有两个主要的”函数”:attr.s和attr.ib()。attr.s是放在类上的装饰器,用于让包为我们初始化魔术方法,而attr.ib()可以(可选)用于定义类的参数。attr.s和attr.ib()都有许多可选参数,文档在:https://www.attrs.org/en/stable/api.html。主要可选参数用于启用/禁用类中的不同魔术方法。
1 2 3 4 5 6 7 8 9
| import attr
@attr.s class Data: x: float = attr.ib(default=None) y: float = attr.ib(default=None) kwargs: typing.Dict = attr.ib(default=None)
class_tester(Data)
|
输出:
1 2 3
| Repr/str魔术方法表示: Data(x=None, y=None, kwargs=None) 相等性魔术方法(使用==)(如果实现应为True): True 相等性魔术方法(使用is)(如果实现应为True): False
|
Attrs深入
Attrs中的验证器
attrs有一个主要功能而dataclasses没有,那就是验证器。这使我们能够确保在创建类时验证输入到任何特定值。让我们构建一个示例,确保我们的参数x大于42,如果不是,则向用户引发错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import attr
@attr.s class ValidatedData: x: float = attr.ib(default=None,validator=attr.validators.instance_of(int)) y: float = attr.ib(default=None) kwargs: typing.Dict = attr.ib(default=None)
@x.validator def more_than_the_meaning_of_life(self, attribute, value): if not value >= 42: raise ValueError("必须大于生命的意义!")
test_data_point_1 = ValidatedData(42) test_data_point_2 = ValidatedData(-35)
|
Attrs中的转换器
转换器用于在创建类时清理输入数据。如果我们想支持用户创建我们打算为整数的参数,我们可以使用转换器清理此输入。这使我们的类对用户更加灵活,同时仍保持参数背后类型的稳定性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import attr
@attr.s class ConvertedData: x: float = attr.ib(default=None,converter=int) y: float = attr.ib(default=None) kwargs: typing.Dict = attr.ib(default=None)
@x.validator def more_than_the_meaning_of_life(self, attribute, value): if not value >= 42: raise ValueError("必须大于生命的意义!")
test_data_point_1 = ConvertedData(42) print(test_data_point_1)
test_data_point_2 = ConvertedData("42") print(test_data_point_2)
|
输出:
1 2
| ConvertedData(x=42, y=None, kwargs=None) ConvertedData(x=42, y=None, kwargs=None)
|
Attrs的编程式创建
在某些情况下,您可能希望以编程方式创建类,好吧,attrs不会让我们失望,并为我们提供了一种方法!我们可以轻松地传递所需的所有参数字典。
1 2 3 4 5 6 7 8
| ProgrammaticData = attr.make_class("Data", {'x': attr.ib(default=None), 'y': attr.ib(default=None), 'kwargs': attr.ib(default=None)} )
print(Data()) print(ProgrammaticData())
|
输出:
1 2
| Data(x=None, y=None, kwargs=None) Data(x=None, y=None, kwargs=None)
|
Pydantic Dataclasses
Pydantic是一个使用Python类型注解进行数据验证和设置管理的Python包。完美,这正是我们试图用dataclasses和attrs做的事情。更重要的是,pydantic提供了一个dataclass装饰器,以在我们的dataclasses上启用数据验证。这使我们能够创建具有数据验证的可扩展类,甚至比attrs更容易!
这里的最大好处是,默认情况下,类型注解在运行时被强制执行,任何无效数据都会引发格式良好的错误。
1 2 3 4 5 6 7 8 9 10
| from pydantic.dataclasses import dataclass import typing
@dataclass class Data: x: float = None y: float = None kwargs: typing.Dict = None
class_tester(Data)
|
输出:
1 2 3
| Repr/str魔术方法表示: Data(x=None, y=None, kwargs=None) 相等性魔术方法(使用==)(如果实现应为True): True 相等性魔术方法(使用is)(如果实现应为True): False
|
pydantic还自动实现转换和数据验证,让我们测试一下。
1 2 3 4
| test_data_point = Data(x='123') print(test_data_point)
test_data_point_2 = Data(x='t')
|
输出:
1 2 3 4
| Data(x=123.0, y=None, kwargs=None) ValidationError: 1 validation error for Data x value is not a valid float (type=type_error.float)
|
正如我们在上面看到的,当数据验证失败时,它向开发人员提供了一个格式良好的错误消息,并在需要时平滑地清理输入。