14

如何将值从基础数据类升级到继承自它的值?

示例(Python 3.7.2)

from dataclasses import dataclass

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

@dataclass
class Friend(Person):

    # ... more fields

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

friend = Friend(name='Alex')
f1.say_hi()

打印“嗨亚历克斯”

random_stranger = Person(name = 'Bob', smell='OK')

返回随机陌生人“人(名称='鲍勃',气味='OK')”

如何将 random_stranger 变成朋友?

Friend(random_stranger)

返回“朋友(姓名=人(姓名='鲍勃',气味='OK'),气味='好')”

结果我想得到“朋友(名字='鲍勃',气味='OK')”。

Friend(random_stranger.name, random_stranger.smell)

有效,但是如何避免复制所有字段?

还是我不能在从数据类继承的类上使用 @dataclass 装饰器?

4

3 回答 3

13

您所要求的是通过工厂方法模式@classmethod实现的,并且可以使用关键字直接在python类中实现。

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

import dataclasses

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

    @classmethod
    def from_instance(cls, instance):
        return cls(**dataclasses.asdict(instance))

从此基类继承的任何新数据类现在都可以创建彼此的实例[1],如下所示:

@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 参数,则它将不起作用。

于 2019-03-08T14:26:58.657 回答
3

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

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,也是抽象的,并使您的具体类继承这两者。

你也可以考虑使用优秀的、向后移植的、更灵活的attrs包。这也将使您能够使用以下evolve()功能创建一个新对象:

friend = attr.evolve(random_stranger, status=RelationshipStatus.FRIEND)
于 2019-02-22T11:19:34.490 回答
0

vars(stranger)为您提供数据类实例的所有属性的字典stranger。由于数据类的默认__init__()方法采用关键字参数,twin_stranger = Person(**vars(stranger))因此使用值的副本创建一个新实例。如果您提供以下附加参数,这也适用于派生类stranger_got_friend = Friend(**vars(stranger), city='Rome')

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
于 2020-03-25T21:52:01.837 回答