dataclass 工具,通过多态扩展
项目描述
dataclassish
从 dataclasses
中提取的工具,扩展到整个Python
Python的 dataclasses
为对象操作提供工具,但仅限于兼容 @dataclass
对象。😢
这个存储库是这些工具的子集,并将它们扩展到可以应用于你想要的任何Python对象!🎉
你可以轻松地为特定对象的方法进行注册,并使用统一的接口进行对象操作。🕶️
例如,
from dataclassish import replace # New object, replacing select fields
d1 = {"a": 1, "b": 2.0, "c": "3"}
d2 = replace(d1, c=3 + 0j)
print(d2)
# {'a': 1, 'b': 2.0, 'c': (3+0j)}
安装
pip install dataclassish
文档
正在制作中。但如果你已经使用过 dataclass
,那么你基本上已经知道你需要知道的一切。
入门
在这个例子中,我们将展示如何在处理 @dataclass
对象时,dataclassish
与 dataclasses
完全相同。
from dataclassish import replace
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
p = Point(1.0, 2.0)
print(p)
# Point(x=1.0, y=2.0)
p2 = replace(p, x=3.0)
print(p2)
# Point(x=3.0, y=2.0)
现在我们将处理一个 dict
对象。请注意,你不能在 dict
对象上使用来自 dataclasses
的工具。
from dataclassish import replace
p = {"x": 1, "y": 2.0}
print(p)
# {'x': 1, 'y': 2.0}
p2 = replace(p, x=3.0)
print(p2)
# {'x': 3.0, 'y': 2.0}
# If we try to `replace` a value that isn't in the dict, we'll get an error
try:
replace(p, z=None)
except ValueError as e:
print(e)
# invalid keys {'z'}.
在自定义类型中注册非常简单!让我们创建一个自定义对象并定义 replace
将如何在此对象上操作。
from typing import Any
from plum import dispatch
class MyClass:
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
def __repr__(self) -> str:
return f"MyClass(a={self.a},b={self.b},c={self.c})"
@dispatch
def replace(obj: MyClass, **changes: Any) -> MyClass:
current_args = {k: getattr(obj, k) for k in "abc"}
updated_args = current_args | changes
return MyClass(**updated_args)
obj = MyClass(1, 2, 3)
print(obj)
# MyClass(a=1,b=2,c=3)
obj2 = replace(obj, c=4.0)
print(obj2)
# MyClass(a=1,b=2,c=4.0)
添加第二个参数
replace
还可以接受一个第二位置的参数,这是一个指定嵌套替换的字典。例如,考虑以下字典:
p = {"a": {"a1": 1, "a2": 2}, "b": {"b1": 3, "b2": 4}, "c": {"c1": 5, "c2": 6}}
使用 replace
,子字典可以通过以下方式更新
replace(p, {"a": {"a1": 1.5}, "b": {"b2": 4.5}, "c": {"c1": 5.5}})
# {'a': {'a1': 1.5, 'a2': 2}, 'b': {'b1': 3, 'b2': 4.5}, 'c': {'c1': 5.5, 'c2': 6}}
相比之下,在纯 Python 中这将是这样:
from copy import deepcopy
newp = deepcopy(p)
newp["a"]["a1"] = 1.5
newp["b"]["b2"] = 4.5
newp["c"]["c1"] = 5.5
这是一个最简单的情况,其中 dict
的可变性允许我们复制整个对象并在之后更新它。注意,我们必须使用 deepcopy
来避免修改子字典。那么如果对象是不可变的呢?
@dataclass(frozen=True)
class Object:
x: float | dict
y: float
@dataclass(frozen=True)
class Collection:
a: Object
b: Object
p = Collection(Object(1.0, 2.0), Object(3.0, 4.0))
print(p)
Collection(a=Object(x=1.0, y=2.0), b=Object(x=3.0, y=4.0))
replace(p, {"a": {"x": 5.0}, "b": {"y": 6.0}})
# Collection(a=Object(x=5.0, y=2.0), b=Object(x=3.0, y=6.0))
使用 replace
,这仍然是一条单行代码。替换任何结构的任何部分,无论嵌套程度如何。
为了区分字典字段和嵌套结构,请使用 F
标记。
from dataclassish import F
replace(p, {"a": {"x": F({"thing": 5.0})}})
# Collection(a=Object(x={'thing': 5.0}, y=2.0),
# b=Object(x=3.0, y=4.0))
dataclass 工具
dataclasses
除了 replace
之外,还有许多实用函数:fields
、asdict
和 astuple
。 dataclassish
支持所有这些函数。
from dataclassish import fields, asdict, astuple
p = Point(1.0, 2.0)
print(fields(p))
# (Field(name='x',...), Field(name='y',...))
print(asdict(p))
# {'x': 1.0, 'y': 2.0}
print(astuple(p))
# (1.0, 2.0)
dataclassish
将这些函数扩展到 dict
上
p = {"x": 1, "y": 2.0}
print(fields(p))
# (Field(name='x',...), Field(name='y',...))
print(asdict(p))
# {'x': 1.0, 'y': 2.0}
print(astuple(p))
# (1.0, 2.0)
支持自定义对象的实现可以与 replace
类似。
转换器
虽然 dataclasses.field
本身不允许使用转换器(见 PEP 712),但许多类似 dataclasses 的库都允许。一个非常简短、非常不完整的列表包括:attrs
和 equinox
。模块 dataclassish.converters
提供了一些有用的转换函数。如果你需要更多,请查看 attrs
!
from attrs import define, field
from dataclassish.converters import Optional, Unless
@define
class Class1:
attr: int | None = field(default=None, converter=Optional(int))
obj = Class1()
print(obj.attr)
# None
obj = Class1(a=1)
print(obj.attr)
# 1
@define
class Class2:
attr: float | int = field(converter=Unless(int, converter=float))
obj = Class2(1)
print(obj.attr)
# 1
obj = Class2("1")
print(obj.attr)
# 1.0
引用
如果你喜欢使用这个库,并且想要引用你使用的软件,请点击上面的链接。
开发
我们欢迎贡献!
项目详情
下载文件
下载适用于您平台的应用程序。如果您不确定选择哪个,请了解更多关于 安装包 的信息。