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.
The post-init function is an in-built function in python and helps us to initialize a variable outside the __init__ function. post-init function in python.
A dataclass can very well have regular instance and class methods. Dataclasses were introduced from Python version 3.7. For Python versions below 3.7, it has to be installed as a library.
Python introduced the dataclass in version 3.7 (PEP 557). The dataclass allows you to define classes with less code and more functionality out of the box.
A data class is a class typically containing mainly data, although there aren't really any restrictions. It is created using the new @dataclass decorator, as follows: from dataclasses import dataclass @dataclass class DataClassCard: rank: str suit: str.
Here is another solution.
Define DefaultVal
and NoneRefersDefault
types:
from dataclasses import dataclass, fields
@dataclass
class DefaultVal:
val: Any
@dataclass
class NoneRefersDefault:
def __post_init__(self):
for field in fields(self):
# if a field of this data class defines a default value of type
# `DefaultVal`, then use its value in case the field after
# initialization has either not changed or is None.
if isinstance(field.default, DefaultVal):
field_val = getattr(self, field.name)
if isinstance(field_val, DefaultVal) or field_val is None:
setattr(self, field.name, field.default.val)
Usage:
@dataclass
class Specs3(NoneRefersDefault):
a: str
b: str = DefaultVal('Bravo')
c: str = DefaultVal('Charlie')
r3 = Specs3('Apple', None, 'Cherry') # Specs3(a='Apple', b='Bravo', c='Cherry')
EDIT #1: Rewritten NoneRefersDefault
such that the following is possible as well:
@dataclass
r3 = Specs3('Apple', None) # Specs3(a='Apple', b='Bravo', c='Charlie')
EDIT #2: Note that if no class inherits from Spec
, it might be better to have no default values in the dataclass and a "constructor" function create_spec
instead:
@dataclass
class Specs4:
a: str
b: str
c: str
def create_spec(
a: str,
b: str = None,
c: str = None,
):
if b is None:
b = 'Bravo'
if c is None:
c = 'Charlie'
return Spec4(a=a, b=b, c=c)
also see dataclass-abc/example
The simple solution is to just implement the default arguments in __post_init__()
only!
@dataclass
class Specs2:
a: str
b: str
c: str
def __post_init__(self):
if self.b is None:
self.b = 'Bravo'
if self.c is None:
self.c = 'Charlie'
(Code is not tested. If I got some detail wrong, it wouldn't be the first time)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With