Python:Dataclass继承自基类Dataclass,如何将值从基类升级到新类?

Python:Dataclass继承自基类Dataclass,如何将值从基类升级到新类?,python,python-3.7,python-dataclasses,Python,Python 3.7,Python Dataclasses,如何将基本数据类的值升级为从其继承的值 示例(Python 3.7.2) 打印“你好,亚历克斯” 返回随机“陌生人”(姓名='Bob',气味='OK')” 我如何把陌生人变成朋友 Friend(random_stranger) 返回“Friend(name=Person(name='Bob',嗅觉='OK'),嗅觉='good')” 因此,我想得到“Friend(name='Bob',嗅觉='OK')” Friend(random_stranger.name, random_stranger.

如何将基本数据类的值升级为从其继承的值

示例(Python 3.7.2)

打印“你好,亚历克斯”

返回随机“陌生人”(姓名='Bob',气味='OK')”

我如何把陌生人变成朋友

Friend(random_stranger)
返回“Friend(name=Person(name='Bob',嗅觉='OK'),嗅觉='good')”

因此,我想得到“Friend(name='Bob',嗅觉='OK')”

Friend(random_stranger.name, random_stranger.smell)
可以,但如何避免复制所有字段


或者可能我不能在继承自数据类的类上使用@dataclass装饰器吗?

您可能不希望类本身是一个可变属性,而是使用诸如枚举之类的东西来指示这样的状态。根据需求,您可以考虑以下几种模式之一:

class RelationshipStatus(Enum):
    STRANGER = 0
    FRIEND = 1
    PARTNER = 2

@dataclass
class Person(metaclass=ABCMeta):
    full_name: str
    smell: str = "good"
    status: RelationshipStatus = RelationshipStatus.STRANGER

@dataclass
class GreetablePerson(Person):
    nickname: str = ""

    @property
    def greet_name(self):
        if self.status == RelationshipStatus.STRANGER:
            return self.full_name
        else:
            return self.nickname

    def say_hi(self):
        print(f"Hi {self.greet_name}")

if __name__ == '__main__':
    random_stranger = GreetablePerson(full_name="Robert Thirstwilder",
                                      nickname="Bobby")
    random_stranger.status = RelationshipStatus.STRANGER
    random_stranger.say_hi()
    random_stranger.status = RelationshipStatus.FRIEND
    random_stranger.say_hi()
您可能还希望以trait/mixin样式实现这一点。与其创建一个
GreetablePerson
,不如创建一个类
Greetable
,同时将其抽象,并使您的具体类继承这两个类

<>你也可以考虑使用优秀的、更靠得住的、更灵活的软件包。这还将使您能够使用
evolve()
函数创建一个新对象:

friend = attr.evolve(random_stranger, status=RelationshipStatus.FRIEND)

您所要求的是由实现的,并且可以使用
@classmethod
关键字直接在python类中实现

只需在基类定义中包含dataclass工厂方法,如下所示:

import dataclasses

@dataclasses.dataclass
class Person:
    name: str
    smell: str = "good"

    @classmethod
    def from_instance(cls, instance):
        return cls(**dataclasses.asdict(instance))
@dataclasses.dataclass
class Friend(Person):
    def say_hi(self):        
        print(f'Hi {self.name}')

random_stranger = Person(name = 'Bob', smell='OK')
friend = Friend.from_instance(random_stranger)
print(friend.say_hi())
# "Hi Bob"
从该基类继承的任何新数据类现在都可以创建彼此的实例[1],如下所示:

import dataclasses

@dataclasses.dataclass
class Person:
    name: str
    smell: str = "good"

    @classmethod
    def from_instance(cls, instance):
        return cls(**dataclasses.asdict(instance))
@dataclasses.dataclass
class Friend(Person):
    def say_hi(self):        
        print(f'Hi {self.name}')

random_stranger = Person(name = 'Bob', smell='OK')
friend = Friend.from_instance(random_stranger)
print(friend.say_hi())
# "Hi Bob"

[1] 如果您的子类引入了没有默认值的新字段,您尝试从子类实例创建父类实例,或者您的父类只有init参数,则此操作将不起作用。

vars(陌生人)
为您提供了dataclass实例
陌生人
的所有属性的dict。由于DataClass的默认
\uuuuu init\uuuuuuuuuuuuuuuo()
方法采用关键字参数,
twin\u陌生人=Person(**vars(陌生人))
创建一个新实例,其中包含值的副本。如果您提供了额外的参数,如
陌生人\u got\u friend=friend(**vars(陌生人),city='Rome')
,则这也适用于派生类:


也许创建一个方法
def been_friend(self):返回
class Person的friend(self.name、self.嗅觉、其他_参数)
?还不熟悉数据类,但您尝试过改变类吗?同学。在为等价而设计的类上运行良好(状态机模式)。@JLPeyret这听起来像是灾难的良方。您不应该使用
vars
来获取数据类的属性,
dataclass。asdict
更好
vars
将只包括init和class变量,如果使用
\uuuuu插槽\uuuuuu
存储属性,则这些属性都不支持,因此不起作用。这就是为什么
dataclass.asdict
存在的原因。
dataclass.asdict
在我的用例中不起作用,因为它递归地转换子数据类(doc:“数据类、dict、列表和元组被递归为”)。我的数据类通常有子数据类,这些子数据类应该保留为数据类。是的,我可以看出它对您的案例不起作用。在
Person
中设置
metaclass=ABCMeta
的目的是什么?类
Person
仍然可以直接实例化。你说得对。为了使其不可实例化,必须添加
抽象方法
,然后在尝试实例化时抛出异常。我不知道我是否同意需要一个
抽象方法
,我可能认为这是一个遗漏。但python对于类型错误往往是非攻击性的,所以这可能是python哲学的问题。为了清楚起见,我将保留它,并且可能也不会添加抽象方法,除非它代表
Person
的通用接口的一部分。在这种情况下,
say_hi()
可能是合适的。
from dataclasses import dataclass


@dataclass
class Person:
    name: str
    smell: str


@dataclass
class Friend(Person):
    city: str

    def say_hi(self):
        print(f'Hi {self.name}')


friend = Friend(name='Alex', smell='good', city='Berlin')
friend.say_hi()  # Hi Alex
stranger = Person(name='Bob', smell='OK')
stranger_got_friend = Friend(**vars(stranger), city='Rome')
stranger_got_friend.say_hi()  # Hi Bob