Consider the following python code:
from dataclasses import dataclass
@dataclass
class Registration:
category: str = 'new'
@dataclass
class Car:
make: str = None
category: str = None
reg: Registration = None
def __post_init__(self):
''' fill in any missing fields from the registration of car '''
if self.reg:
for var in vars(self.reg):
if not self.var:
self.var = self.reg.var
r = Registration()
a = Car(make='ford', category='used', reg=r)
# its unknown if b is used/new, so we explicitly pass it None
b = Car(make='ford', category=None, reg=r)
In above example, the __post_init__
is supposed to fill in fields in Car
class if it was not passed in during creation of Car
object. However if None was explicitly passed in as the field value (in this case for category
) it's not supposed to overwrite it from the Registration object. But the above code does. How do I detect what values were explicitly passed in during the object creation vs what are defaults?
I'd be surprised if there were a way to distinguish between
a None
passed explicitly vs one that the object acquired via
its defaults. In situations like yours, one technique is to use
a kind sentinel value as the default.
@dataclass
class Car:
NO_ARG = object()
make: str = None
category: str = NO_ARG
reg: Registration = None
def __post_init__(self):
if self.reg:
for var in vars(self.reg):
if getattr(self, var) is self.NO_ARG:
setattr(self, var, getattr(self.reg, var))
However, you might also take the awkward situation you find yourself in as a signal that perhaps there's a better way to model your objects. Without knowing more about the broader context it's difficult to offer definitive advice, but I would say that your current strategy strikes me as fishy, so I would encourage you to thinks some more about your OO plan.
To give one example of an alternative model, rather than using the Registration to overwrite the attributes of a Car, you could instead build a property to expose the Registration attribute when the Car attribute is missing. A user of the class can decide whether they want the category strictly from the Car or they are happy to take the fallback value from the Registration, if available. This approach comes with tradeoffs as well.
@dataclass
class Car:
make: str = None
category: str = None
reg: Registration = None
@property
def category_reg(self):
if self.category is None and self.reg:
return self.reg.category
else:
return self.category