提问人:Ein Google-Nutzer 提问时间:10/13/2023 最后编辑:Ein Google-Nutzer 更新时间:10/14/2023 访问量:95
修改数据类对象,以便仅覆盖指定的元素
Modifying a dataclass object such that only specified elements are overridden
问:
我想创建一个包含大量成员元素的数据类。此 dataclass 不应具有成员,以确保完整信息在对象中可用。A
Optional
然后我想要一个“修改选项”,它的成员与 相同,但作为可选成员。A
在不需要将成员写成两个不同类的情况下,最好的方法是什么?
这是我的方法(工作示例):
from copy import deepcopy
from dataclasses import dataclass
from typing import Optional
@dataclass
class A:
x: int
y: int
@dataclass
class A_ModificationOptions:
x: Optional[int] = None
y: Optional[int] = None
def modifyA(original: A, modification: A_ModificationOptions):
if modification.x is not None:
original.x = deepcopy(modification.x)
if modification.y is not None:
original.y = deepcopy(modification.y)
original_A = A(x=1, y=2)
print("A before modification: ", original_A) # A(x=1, y=2)
modification_A = A_ModificationOptions(y=7)
modifyA(original_A, modification_A)
print("A after modification: ", original_A) # A(x=1, y=7)
此代码满足以下要求:
- 原始成员没有可选成员,因此必须已设置所有成员。
A
- 在修改中,只需要设置需要调整的成员。
A
此代码不满足以下要求:
- 我不想再次“复制”每个成员。
A
A_ModificationOptions
- 如果可能的话,我不想拥有该功能,而是内置的东西。
modifyA()
- 如果 2 是不可能的:我不想为 into 的每个成员添加 2 行。
A
modifyA
有没有一种巧妙的方法来存储稀疏的“修改选项”,用于潜在的巨大数据类?
用例:用户创建一次完整列表,然后在不同的场景中,他可以玩弄该完整列表的增量,并且必须以某种方式存储完整列表的“增量”-> 所以我想到了一个原始的完整列表类和一个“增量”类,但我希望这在某种程度上可以以更简洁的方式完成。也许是智能深度复制之类的东西?A
A_ModificationOptions
更新1:
谢谢@wjandrea的反馈!您对第 3 点的解决方案没有考虑更深的嵌套数据类,因此我使用您的建议使其适用于嵌套数据类。下面的代码现在解决了第 3 点:
from copy import deepcopy
from dataclasses import dataclass, is_dataclass
from typing import Optional
class Original:
pass
@dataclass
class B(Original):
a1: int
a2: int
a3: int
@dataclass
class A(Original):
x: int
y: int
b: B
class Modification:
pass
@dataclass
class B_Mod(Modification):
a1: Optional[int] = None
a2: Optional[int] = None
a3: Optional[int] = None
@dataclass
class A_Mod(Modification):
x: Optional[int] = None
y: Optional[int] = None
b: Optional[B_Mod] = None
def modifyDataclass(original: Original, modification: Modification):
assert is_dataclass(original) and is_dataclass(modification)
for k, v in vars(modification).items():
if is_dataclass(v):
assert isinstance(v, Modification)
modifyDataclass(original=getattr(original, k), modification=v)
return
if v is not None:
setattr(original, k, v)
original_A = A(x=1, y=2, b=B(a1=3, a2=4, a3=5))
print(
"A before modification: ", original_A
) # A(x=1, y=2, b=B(a1=3, a2=4, a3=5))
modification_A = A_Mod(y=7, b=B_Mod(a2=19))
modifyDataclass(original_A, modification_A)
print(
"A after modification: ", original_A
) # A(x=1, y=7, b=B(a1=3, a2=19, a3=5))
现在,如果第 1 点和第 2 点有解决方案,那就太棒了!
也许也以某种方式衍生?就像A_Mod是 A 的孩子,但后来将所有成员都切换为可选成员?
答:
我想我明白你想要什么,这里有一种动态生成类的方法。正如注释中所指出的,这永远不会通过静态类型检查器。如果你想运行类似或的东西,你将不得不退出修改选项。这是 Python 中非常动态的反映。A_ModificationOptions
mypy
pyright
Any
现在,几点说明。 是一个装饰器,就像任何装饰器一样,它只是在事后应用于类。那是dataclass
@dataclass
class X:
...
只是
class X:
...
X = dataclass(X)
因此,如果我们愿意,我们可以像普通的 Python 函数一样在我们组成的类上调用。当我们讨论这个话题时,我们也可以使用普通的 Python 制作类。type
有一个三参数形式,它充当新类的构造函数。dataclass
class type(name, bases, dict, **kwds)
因此,让我们看看我们实际上是如何做到这一点的。我们需要和.我还导入以获得技术上正确的注释,尽管它不影响语义。dataclass
fields
Optional
from dataclasses import dataclass, fields
from typing import Optional
现在魔术酱,为方便起见评论。
def make_modification_dataclass(original_dataclass, new_class_name=None):
# Provide a default name if the caller doesn't supply a custom
# name for the new class.
if new_class_name is None:
new_class_name = original_dataclass.__name__ + "_ModificationOptions"
# This actually creates the class. @dataclass is going to look at
# the __annotations__ field on the class, which is normally
# generated by writing type annotations in Python code. But it's
# explicitly defined to be a mutable dictionary, so we're well
# within our rights to create and mutate it ourselves.
new_class = type(new_class_name, original_dataclass.__bases__, {
"__annotations__": {}
})
# Iterate over all of the fields of the original dataclass.
for field in fields(original_dataclass):
# For each field, put a type in __annotations__. The type
# could be anything as far as @dataclass is concerned, but we
# make it Optional[whatever], which is actually the correct
# type. No static type checker will ever see this, but other
# tools that analyze __annotations__ at runtime will see a
# correct type annotation.
new_class.__annotations__[field.name] = Optional[field.type]
# We also need to set the attribute itself on the class. This
# is the "= None" part of the A_ModificationOptions class you
# wrote, and it will show @dataclass what the default value of
# the field should be.
setattr(new_class, field.name, None)
# Apply the decorator and return our brand new class.
return dataclass(new_class)
要使用它,我们只需传递原始类并将结果分配给一个名称。
@dataclass
class A:
x: int
y: int
# This is making a class. A real, genuine dataclass.
A_ModificationOptions = make_modification_dataclass(A)
您的函数有点接近 dataclasses.replace
,但后者 (a) 接受字典,(b) 返回一个新实例而不是就地更改。幸运的是,编写我们自己的代码相当简单。modifyA
这基本上是 wjandrea 在评论中建议的。我只是更喜欢使用而不是,因为它可以保证只获取数据类字段,而不是从非数据类超类或四处闲逛并做有趣业务的人那里获得任何额外的内容。dataclasses.fields
vars
def modify(original, modification):
for field in fields(modification):
value = getattr(modification, field.name)
if value is not None:
setattr(original, field.name, value)
您的代码将按建议工作。
original_A = A(x=1, y=2)
print("A before modification: ", original_A) # A(x=1, y=2)
modification_A = A_ModificationOptions(y=7)
modify(original_A, modification_A)
print("A after modification: ", original_A) # A(x=1, y=7)
我重命名了该函数,因为它实际上从未执行任何特定于 .这个函数将适用于任何和相应的类。无需重写它,即使是表面上的。modify
modifyA
A
@dataclass
_ModificationOptions
评论
vars()
setattr()
for k, v in vars(modification).items(): if v is not None: setattr(original, k, v)
typing
dataclass