Search code examples
pythonpython-3.7python-dataclasses

Python: Dataclass that inherits from base Dataclass, how do I upgrade a value from base to the new class?


How can I upgrade values from a base dataclass to one that inherits from it?

Example (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()

prints "Hi Alex"

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

return for random_stranger "Person(name='Bob', smell='OK')"

How do I turn the random_stranger into a friend?

Friend(random_stranger)

returns "Friend(name=Person(name='Bob', smell='OK'), smell='good')"

I'd like to get "Friend(name='Bob', smell='OK')" as a result.

Friend(random_stranger.name, random_stranger.smell)

works, but how do I avoid having to copy all fields?

Or is it possible that I can't use the @dataclass decorator on classes that inherit from dataclasses?


Solution

  • What you are asking for is realized by the factory method pattern, and can be implemented in python classes straight forwardly using the @classmethod keyword.

    Just include a dataclass factory method in your base class definition, like this:

    import dataclasses
    
    @dataclasses.dataclass
    class Person:
        name: str
        smell: str = "good"
    
        @classmethod
        def from_instance(cls, instance):
            return cls(**dataclasses.asdict(instance))
    

    Any new dataclass that inherit from this baseclass can now create instances of each other[1] like this:

    @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] It won't work if your child classes introduce new fields with no default values, you try to create parent class instances from child class instances, or your parent class has init-only arguments.