
在 python 中,attrs 库提供了一种简洁而强大的方式来定义结构化数据类,它通过装饰器和类型提示极大地简化了样板代码。当我们需要将外部数据(如 json 或字典)映射到这些定义好的 attrs 类时,通常会遇到嵌套结构的处理问题。
考虑一个场景,我们有一组角色数据,每个角色包含姓名信息,并且这些角色共同构成一个团队。数据结构如下所示:
data_source = {
"characters": [
{"first_name": "Duffy", "last_name": "Duck"},
{"first_name": "Bugs", "last_name": "Bunny"},
# ... 更多角色
]
}我们希望将其转换为 attrs 类实例:
from attrs import define, field
from typing import List
@define(kw_only=True)
class Character:
first_name: str
last_name: str
@define
class LooneyToons:
characters: List[Character] = field(factory=list) # 初始定义,稍后解释为何移除converterattrs 提供了 converter 参数,用于在字段赋值时对输入值进行转换。例如,some_field: int = field(converter=int) 可以确保输入值被转换为整数。然而,当处理列表中的复杂对象转换时,直接使用 converter=Character 会遇到问题:
# 错误示例:直接将 Character 作为 List[Character] 的 converter
@define
class LooneyToons_Problematic:
characters: List[Character] = field(factory=list, converter=Character)
# 尝试使用:
# LooneyToons_Problematic(characters=data_source['characters'])
# 这将导致 TypeError: Character.__init__() takes 1 positional argument but 2 were given这个错误的原因是,attrs 的 converter 期望一个函数,该函数接收单个值并将其转换为目标类型。当我们将其应用于一个 List[Character] 字段时,attrs 会尝试将整个列表 data_source['characters'] 作为参数传递给 Character 类的构造函数(即 Character.__init__),这显然是不正确的,因为 Character 期望的是 first_name 和 last_name 这样的关键字参数,而不是一个字典列表。
一种可行的解决方案是手动遍历列表,并为每个字典创建 Character 实例:
# 手动转换示例
looney_tunes_instance = LooneyToons(
characters=[Character(**x) for x in data_source['characters']]
)
print(looney_tunes_instance)
# 输出: LooneyToons(characters=[Character(first_name='Duffy', last_name='Duck'), ...])这种方法虽然有效,但在以下情况下显得不够优雅或高效:
为了更优雅、自动化地处理 attrs 类与复杂嵌套数据(如字典、列表)之间的转换,cattrs 库应运而生。cattrs 是一个强大的工具,专门用于在 Python 对象和原始数据类型之间进行结构化(structuring)和非结构化(unstructuring)。它通过利用类型提示,能够智能地解析复杂的数据结构并自动执行深层转换。
使用 cattrs 解决上述问题非常简单:
移除 converter 参数: 在 LooneyToons 类的 characters 字段定义中,我们不再需要 converter 参数。cattrs 将根据类型提示 List[Character] 自动推断出正确的转换逻辑。
使用 cattrs.structure: 调用 cattrs.structure() 函数,将原始字典数据和目标 attrs 类作为参数传入。
以下是完整的 cattrs 解决方案代码:
from typing import List
from attrs import define, field
from cattrs import structure # 导入 cattrs 的 structure 函数
# 示例数据
data_source = {
"characters": [
{"first_name": "Duffy", "last_name": "Duck"},
{"first_name": "Bugs", "last_name": "Bunny"},
{"first_name": "Sylvester", "last_name": "Pussycat"},
{"first_name": "Elmar", "last_name": "Fudd"},
{"first_name": "Tweety", "last_name": "Bird"},
{"first_name": "Sam", "last_name": "Yosemite"},
{"first_name": "Wile E.", "last_name": "Coyote"},
{"first_name": "Road", "last_name": "Runner"},
]
}
# 定义内部的 Character 类
@define(kw_only=True)
class Character:
first_name: str
last_name: str
# 定义外部的 LooneyToons 类,注意移除了 converter 参数
@define
class LooneyToons:
characters: List[Character] = field(factory=list)
# 使用 cattrs.structure 进行转换
looney_tunes_instance = structure(data_source, LooneyToons)
print(looney_tunes_instance)
# 预期输出:
# LooneyToons(characters=[Character(first_name='Duffy', last_name='Duck'), Character(first_name='Bugs', last_name='Bunny'), Character(first_name='Sylvester', last_name='Pussycat'), Character(first_name='Elmar', last_name='Fudd'), Character(first_name='Tweety', last_name='Bird'), Character(first_name='Sam', last_name='Yosemite'), Character(first_name='Wile E.', last_name='Coyote'), Character(first_name='Road', last_name='Runner')])
# 验证类型
print(isinstance(looney_tunes_instance.characters[0], Character)) # True在这个例子中,cattrs.structure(data_source, LooneyToons) 会执行以下操作:
通过结合 attrs 定义清晰的数据模型和 cattrs 自动化数据映射,开发者可以更高效、更优雅地处理 Python 中的复杂数据结构,显著提高代码的可读性、可维护性和健壮性。
以上就是attrs 与 cattrs:优雅处理嵌套数据结构的教程的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号