python 修改一个类对象,使其只覆盖指定的元素

qfe3c7zg  于 2023-10-14  发布在  Python
关注(0)|答案(1)|浏览(62)

我想创建一个有很多成员元素的类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)

此代码满足以下要求:
1.原始A没有可选成员,因此必须设置所有可选成员。
1.在A的修改中,只需要设置需要适配的成员。
此代码不满足以下要求:
1.我不想再次将A的每个成员“复制”到A_ModificationOptions中。
1.如果可能的话,我不想有modifyA()功能,但内置的东西。
1.如果2是不可能的:我不想在modifyA中为每个A成员添加2行。
有没有一个简洁的方法来存储一个潜在的巨大的类稀疏的“修改选项”?
用例:用户创建一个完整的列表,然后在不同的场景中,他可以使用完整列表的增量,并且完整列表的“增量”必须以某种方式存储->所以我想到了一个原始的完整列表类A和一个“增量”类A_ModificationOptions,但我希望这可以以更简洁的方式实现。也许是像智能深度复制一样的东西?

更新一:

不好感谢你的评分您对第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的子元素,但随后将所有成员切换为可选成员?

eqzww0vc

eqzww0vc1#

我想我明白你想要什么,这里有一个动态生成A_ModificationOptions类的方法。正如注解中所指出的,这将 * 永远 * 通过静态类型检查器。如果你想运行类似mypypyright的东西,你将不得不Any的修改选项。这是Python中非常动态的反射。
现在,几个音符。dataclass是一个装饰器,就像任何装饰器一样,它只是在事后应用于一个类。也就是说,

@dataclass
class X:
    ...

只是

class X:
    ...
X = dataclass(X)

因此,如果我们选择这样做,我们可以像普通的Python函数一样在我们创建的类上调用dataclass。当我们谈到这个主题时,我们也可以使用普通的Python来创建类。type有一个三参数的形式,它充当新类的构造函数。

class type(name, bases, dict, **kwds)

让我们看看我们是如何做到的。我们需要dataclassfields。我还导入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)

您的modifyA函数与dataclasses.replace有 * 种 * 接近,但后者(a)接受字典,(b)返回新示例而不是原地突变。幸运的是,编写我们自己的代码相当简单。
这基本上就是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的事情。这个函数将适用于 any@dataclass和相应的_ModificationOptions类。不需要重写,即使是表面上的。
在线试用!

相关问题