pythonpython-3.xdefault-valuepython-dataclasses

How to apply default value to Python dataclass field when None was passed?


I need a class that will accept a number of parameters, I know that all parameters will be provided but some maybe passed as None in which case my class will have to provide default values.

I want to setup a simple dataclass with a some default values like so:

@dataclass
class Specs1:
    a: str
    b: str = 'Bravo'
    c: str = 'Charlie'

I would like to be able to get the default value for the second field but still set a value for the third one. I cannot do this with None because it is happily accepted as a value for my string:

r1 = Specs1('Apple', None, 'Cherry') # Specs1(a='Apple', b=None, c='Cherry')

I have come up with the following solution:

@dataclass
class Specs2:
    def_b: ClassVar = 'Bravo'
    def_c: ClassVar = 'Charlie'
    a: str
    b: str = def_b
    c: str = def_c
    
    def __post_init__(self):
        self.b = self.def_b if self.b is None else self.b
        self.c = self.def_c if self.c is None else self.c

Which seems to behave as intended:

r2 = Specs2('Apple', None, 'Cherry') # Specs2(a='Apple', b='Bravo', c='Cherry')

However, I feel it is quite ugly and that I am maybe missing something here. My actual class will have more fields so it will only get uglier.

The parameters passed to the class contain None and I do not have control over this aspect.


Solution

  • I know this is a little late, but inspired by MikeSchneeberger's answer I made a small adaptation to the __post_init__ function that allows you to keep the defaults in the standard format:

    from dataclasses import dataclass, fields
    def __post_init__(self):
        # Loop through the fields
        for field in fields(self):
            # If there is a default and the value of the field is none we can assign a value
            if not isinstance(field.default, dataclasses._MISSING_TYPE) and getattr(self, field.name) is None:
                setattr(self, field.name, field.default)
    

    Adding this to your dataclass should then ensure that the default values are enforced without requiring a new default class.