0

I have a super dataclass (Settings) and multiple children who inherit the settings of the super dataclass (FSettings, BSettings).

How can I pass a dictionary of "arguments" to a child dataclass in a way where it sets them, if applicable?

from dataclasses import dataclass

@dataclass()
class Settings:
    verbose: bool 
    detailed: bool 



@dataclass()
class FSettings(Settings):
    vals: list 
    def __init__(self, args):
        for arg in args:
            if arg in list(self.__annotations__):
                self.__setattr__(arg, args[arg])
        
if __name__ == '__main__':
    args = {'vals': ["a", "b"], 'verbose': True, 'detailed':False}
    s = FSettings(args)
    print(s)

If I print list(self.__annotations__) it shows ['vals'], ignoring verbose and detailed

Adin D
  • 491
  • 1
  • 4
  • 9

2 Answers2

0

(My own answer after trying for an hour)

This appears to work, but you have to default the values to "none", as the FSettings values initiate after the Settings values. Without a default, hasattr will return false.

The init can also be moved to the parent class since it is generic. However, a super().__init__() is needed in the child otherwise the parent values populate wrong (inconsistent error, not entirely sure why)

from dataclasses import dataclass

@dataclass() 
class Settings:
    verbose: bool = None
    detailed: bool = None
    def __init__(self, args):
        for arg in args:
            if hasattr(self, arg):
                self.__setattr__(arg, args[arg])
            else:
                print(f"Settings class could not locate {arg} as a setting. Skipping...")

@dataclass() 
class FSettings(Settings):
    vals: list = None


    def __init__(self, args):
        super().__init__(args)


if __name__ == '__main__':
    args = {'vals': ["a", "b"], 'verbose': True, 'detailed':False}
    s = FSettings(args)
    print(s)
Adin D
  • 491
  • 1
  • 4
  • 9
  • It seems weird to have an `__init__` method on a dataclass when they are automatically generated for you. You could remove it from the `Settings` class and create an instance simply with `s = Settings(**args)`. You might also look at `__post_init__` a special method just for dataclasses so you don't have to overwrite the autogenerated `__init__` – Davos Jan 25 '21 at 14:21
0
from dataclasses import dataclass
from typing import Optional


@dataclass
class Settings:
    verbose: Optional[bool] = None
    detailed: Optional[bool] = None

    def __post_init__(self) -> None:

        args = []
        if self.verbose is None:
            args.append('verbose')

        if self.detailed is None:
            args.append('detailed')

        if args:
            s = f"Settings class could not locate {args} as a setting."
            s += " Skipping..."
            print(s)


@dataclass
class FSettings(Settings):
    vals: Optional[list] = None


args = {'vals': ["a", "b"], 'verbose': True, 'detailed': False}
f_settings = FSettings(**args)

args = {'vals': ["a", "b"]}
f_settings = FSettings(**args)
# Settings class could not locate ['verbose', 'detailed'] as a setting. Skipping..

Evgeniy_Burdin
  • 627
  • 5
  • 14
  • Interesting response, but it is not generic. Your answer requires each variable to be checked in post init. – Adin D Jul 14 '20 at 21:57
  • 1
    You can make import: `from dataclasses import fields`. Then in the loop get each field: `for field in fields(self)`. For each field you can get: `field.name`, `getattr(self, field.name)` - field value, `field.type` - field annotation. Based on this data, you can make any generalized check of fields. – Evgeniy_Burdin Jul 15 '20 at 04:50